reactjsreact-reduxreselect

For what purpose useSelector() and reselect createSelector() was created?


I'm fairly new to React. I read that the main feature of React is "reactivity": you have some state, you pass it to a component, and when the state is changed, the component is rerendered.

Then why useSelector() exists? I read that it is used to select some value from a state, and then it subscribes to changes to that value. Isn't "subscribing" is the default behavior of React, that is the "reactivity"?

OK then. Then they write that useSelector() when something updates the component, is always dragging values from the store, and that is why you should use reselect createSelector(), as it "memoizes" the values. Duh, why dragging the values is bad? As I understand, these values are simply a links to nested data of the state - why memoize them?. And as the values are always the same, the component shouldn't redraw.

Thus, I used to think that I could simply pass the values to a component and then React deals with everything, but they say that I should pass them via useSelector(), and better - to pass via createSelector() and cache them? What's the trick?


Solution

  • You cannot always pass values to a component from its parent because the parent doesn't logically own the value (shared state) or because there are many component in between the component that has the value (with useState for example) and the component that (also) needs the value so to pass it you need to do prop drilling.

    So redux can be used for shared state and/or situations that require prop drilling. You could use React Context as well but any component reading the value from it will re render on changes. So if you have a large object in context and only change one property then all components will re render (re produce jsx, not nesaseraly re paint in DOM), even the ones that would produce the same jsx.

    Redux has selectors so components can select only a part of the whole state (context does not) so the component can select only those parts that are valid for the component. If a state has 100 counters and your page has 100 components showing those counters then changing one counter will only re render one component, you cannot do this with context.

    Reselect is a tool that can be used to compose selectors so instead of writing the full path to the value you are interested in you can re use logic and prevent duplicate implementation. For example; you have a state like {currentUser:{user:user}} then selecting current user would be state.currentUser. Imagine having 50 components selecting current user like this then changing the location or logic of what current user is will cause many places you need to refactor.

    Memoization is automatically but is only important if a selector has an expensive computation or if the selector returns a new object every time. The expensive computation can be skipped when relevant data didn't change and returning a new object would cause the component to re render every time anything in the state changed, even values that didn't change the object returned from the selector.

    For example:

    const selectPerson = (state,personId) => {
      const friends = selectFriends(state)
      //possible duplicate implementation
      const person = state.people[personId]
      //returning new object every time, result of this function
      //  will never be the same
      return {
        ...person,
        friends
      }
    }
    //because a new object is returned the return value will never be equal
    //  even if the state or personId didn't change
    selectPerson(state,22)!==selectPerson(state,22);
    

    In reselect it would look like the following:

      const selectPerson = (personId) =>
        createSelector(
          //re use logic to select a person by id
          [selectPerson(personId), selectFriends],
          //because of memoization the object will not change unless
          //  relevant data in state changed.
          (person, friends) => ({ ...person, friends })
        )
    

    I have written some documentation a while ago that explains this in more detail here