keajs / kea

Batteries Included State Management for React

Home Page:https://keajs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using equality comparison in selectors

rschyboll opened this issue · comments

Hi, first of all, love the project, it's exactly what i needed in my project :)
I have a few questions regarding it, but I'm going to split those questions in separate issues to keep it clean.

My first question is about selectors, is it possible to use some kind of equality comparison in them? I could not find anything in the docs about it. Both reselect, which I think that Kea uses in the background and react-redux which I'm migrating from, have such features.

An example, that could benefit from such a feature:

import { kea } from 'kea';

import type { someLogicType } from './someLogicType';

type WidgetData = {}; 

export const someLogic = kea<someLogicType<WidgetData>>({
  actions: {
    addNewWidget: (widgetKey: string, data: WidgetData) => ({
      widgetKey,
      data,
    }),
  },
  reducers: {
    widgets: [
      {} as { [key: string]: WidgetData }, 
      {
        addNewWidget: (state, { widgetKey, data }) => {
          return { ...state, [widgetKey]: data };
        },
      },
    ],
  },
  selectors: {
    widgetKeys: [
      (selectors) => [selectors.widgets],
      (widgets) => {
        return Object.keys(widgets);
      },
    ],
  },
});

To explain what is going on here, the widgets reducer defines an object, with unique keys, each describing one widget, and in the value is some data, that the rendered widget uses. In the selector "widgetKeys" i'm returning all widget keys, which are used to render all of those widgets later in react. Currently, if any widget data changes, the widget that uses the "widgetKeys" selector rerenders, which is undesirable.

It's also possible that my state structure is wrong. If there is a pattern that would fix my issue, without the need for equality comparisons, I would be really grateful for pointing me in the right direction. Thanks in advance and see you in the next issue :)

Hey, version 2.5.11 is out with this feature.

Now you can do:

import deepEqual from 'fast-deep-equal'

const logic = kea({
  selectors: {
    widgetKeys: [
      (selectors) => [selectors.widgets],
      (widgets) => {
        return Object.keys(widgets);
      },
      null, // PropTypes
      deepEqual
    ],
  },

The third element, PropTypes, is a legacy feature from the olden days, whose removal would be a breaking change. I'll do it in version 3.0, which is actually just around the corner...

and sorry for the wait 😅

Awesome!! Thank you for backporting that feature, but i do have some suggestions, maybe for the 3.0 version in the future 😅

In my use case, with the new feature, i need to create two selectors, one that creates the new array, and a second one, that checks it's equality. I was wondering, if there is not a better solution for this.
When creating selectors in reselect, it is possible to define a separate method for comparing the result value of that selector, maybe it would be possible to have this too in Kea? Maybe allowing the third parameter in the selector definition to be an object with optional fields, similar to reselect? Something like this:

{
  equalityCheck: (a, b) => a === b,
  resultEqualityCheck: shallowEqual
}

Also the current syntax is a bit confusing, when having multiple input parameters to the selector, as one comparison function has to check all of those values, but it seems that could be a limitation of how reselect handles things.

To be honest, I don't fully understand how Kea does work underneath, but it seems to me that having an object with some optional configuration options for a selector would make things easier for adding more options in the future.

Saying all that, looking forward for the 3.0 release 😅

Oh, I didn't know of this new reselect 4.1 feature.

I just released 2.6, and changed the last array element to just directly takes reselect's memoizeOptions. You can do this now:

const logic = kea({
    selectors: {
        widgetKeys: [
            (selectors) => [selectors.widgets],
            (widgets) => Object.keys(widgets),
            null, // PropTypes, will be removed in Kea 3.0
            { resultEqualityCheck: deepEqual },
        ],
    },
})