question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add support for per-scope selector instantiation

See original GitHub issue

When creating a hook, it is possible to provide a selector. Consider this contrived example which will just select a property on the store;

const useValue1 = createHook(Store, {
  selector: (state) => state.value1 + state.value2,
});

When this hook is used the selector will get executed with the up to date state and return the required value. In the docs, it is also mentioned that we can use memoized selectors (e.g reselect). The following example will not execute the (value1, value2) => value1 + value2 selector unless the output of value1Selector or value2Selector (which might also be memoized) changes;

const useValue1 = createHook(Store, {
  selector: createSelector(
    [value1Selector, value2Selector],
    (value1, value2) => value1 + value2
  ),
});

The problem with this is that we are “creating” a selector once when we are creating the hook. If we use this selector with multiple instances of the store (say different containers) the memoization will loose its effectiveness as the single level of memoization will think the values has changed whenever a new mutation (with a potentially different store state) is initiated one after another. (Also see this)

To be able to utilize selectors at their best, we need a way to instantiate selectors together with store instances and this requires accepting a selector “creator” that will get executed once for each scope.

const useValue1 = createHook(Store, {
  // This function will get executed and the resulting selector is tracked internally when necessary
  selector: () => createSelector(
    [value1Selector, value2Selector],
    (value1, value2) => value1 + value2
  ),
});

It maybe ok to have another option named selectorCreator, but this may make the API a little confusing for future users. Changing the default behaviour on the other hand is probably not a good idea.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:17

github_iconTop GitHub Comments

2reactions
anacierdemcommented, Nov 10, 2020

“memoization(in React) is not trying to skip the actual memoized operation, but mitigate cascade updates after it” so if the selector runs again it should not be a problem. And the default Sweet-state behaviour (of shallow compare even the output before letting an update fire) will already stop many scenarios from triggering a re-render, like in the example of favourites on a side panel.

I think I haven’t realized that the output of the selector was shallow compared. I assumed it was a reference equality with a new list breaking the memoization. Sorry about not checking the code first, It is clear in the docs (As long as the user returned by find is shallow equal to the previews one) but somehow tricked me to think o/w. This will indeed prevent many unnecessary updates, and probably would end this issue just from the beginning 🤦 .

Check this out, I was totally wrong;

On the other hand, think about a simple operation where we have id indexed objects in the state and we want to display them in specific order so that in the selector, we create a new array every time the selector gets executed. As we will be creating a new reference each time, the optimisation does not apply for this case.

Still though running all those selectors on every update may build up, but the justification for that is keeping the state small so that we won’t have too many of them, which is acceptable.

We are currently doing the memoisation on a wrapping custom hook and returning useMemoed selectors, which works fine except for action usability. This is preventing any of the problems we have discussed.

I agree that sweet state should not expand its API. One thing coming to my mind though maybe exposing the equality check to give a little bit control to the consumer. The pre-packed comparison is pretty good and fits well with the intention though. Expanding the docs seems the way to go here.

Thanks for the great discussion! I have learned a lot from it.

1reaction
albertogasparincommented, Oct 30, 2020

Well, what I was suggesting was just using the store to hold the instances:

const MyContainer = createContainer(Store, {
  onInit: ({setState, getState}) => {
    setState({
      selectors: {
         first: createSelector(...),
         second: createSelector(...),
      }
    })
  }
});

const useBla = createHook(Store, { 
  selector: (state) => state.selectors.first(state)
});

I agree that having such functionality built-in could be handy, but it needs to be well thought through, both on implementation an behaviour. The current selector implementation is far from perfect (as you have highlighted) so I would be considering changing it (for instance deprecating selector and pushing for selectorCreator) but needs to be rock solid this time.

Because it’s not just about adding new code. There are subtleties that I’d like to get right before extending the library. For example, if you provide a selectorCreator option where should the instance be bound to? For your use case, I get you’d like it to be state (scope) based, so consuming the same selector/hook in various places under the same scope returns the same value. However, if I add props/args to the selector, then it becomes useless again as it will get recomputed every time (as today with scopes) and to be effective the selector instance should be bound to the component/hook itself. But that would reduce the performance in many other cases. So which scenario is more common? Which behaviour produces less surprises?

Should sweet-state instead provide a custom version of createSelector that deals with such nuances so you can create them globally, by scope and by hook instance? As maybe reselect design is not fit for what we need 🤔

Read more comments on GitHub >

github_iconTop Results From Across the Web

xmartlabs/XLActionController: Fully customizable and ... - GitHub
Instantiate custom action sheet controller let actionSheet ... XLActionController provides a way to add action sections as illustrated in the code below:.
Read more >
Model - Magnolia CMS Docs
Interaction between template scripts and their models can be predefined during the conceptual project phase. Model classes and template scripts can be developed ......
Read more >
NAVSEA Warfare Centers Technical Capabilities Manual, Rev 7
Performs combat systems development support for fielded systems, adapting and transitioning new technologies, affecting architectural migration ...
Read more >
Untitled
Add song in photo, 2004 cadillac ext rear shocks, Nesquik chocolate sponge, ... Join me on periscope, Rainbow falls tattoo hilo, Orologio invicta...
Read more >
Untitled
3 mb bild, Sorbiclis ad clistere 120ml, Ixa-w404 brake bypass, Robert kettler, ... Jose norberto fernandez del cotero, Sitefinity cms support!
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found