2

I am new to Redux and I have been reading quite good stuff around it, including memoization technique (especially with reselect). I have a question though and I struggle to find a proper answer. If we add memoization for every single selector (assuming we have a lot), even the simple getters, will it cause performance issues (due to memoized data under the hood maybe?)? Memoization for complex selectors is obviously beneficial as it prevents recomputing if not needed. But I find memoization for simple selectors beneficial as well, to avoid useless rerender.

In fact, I use useSelector hook and the doc states:

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.

So even for a selector returning the same primitive value (say an int), If i am not wrong, the useSelector should always make the component rerender (even if the selector always returns the same value).

If what I am saying is ture, memoizing even simple getters is usefull for that matter, but can overusing it cause other performance issues?

Thanks for helping

OTmn
  • 185
  • 1
  • 2
  • 16
  • Reselect memoization and optimizing code performance is just one of the reasons to use it and better performance is not guaranteed. What is guaranteed is better designed code. As a starting programmer you should try to write functions that **implement** one thing and functions that need to do more than one thing you need to find a way to combine the "implement one thing" functions (composing functions). This is what reselect can do for your selector functions. A function such as `state=>state.one.two` implements 2 things: 1) location of one in state 2) location of two in one – HMR Mar 04 '21 at 17:57

1 Answers1

8

Writing memoized selector functions is useful, but many people try to memoize too many selectors which really shouldn't be memoized.

If a selector function just does a simple lookup and return of existing data from the state, there's no need to memoize it - it could just be a simple function. For example:

const selectTodoById = (state, id) => state.todos.entities[id];

Second, you also probably shouldn't create a separate selector for every single state field. Find a reasonable level of granularity.

Memoized selectors are only useful when the selector returns some derived data. A good example of this is filtering an array:

const selectCompletedTodos = createSelector(
  state => state.todos,
  state => state.filters.completed,
  (todos, completed) => todos.filter(t => t.completed === completed)
);

That way, the derived data is only re-calculated when the input values change.

As a side note, I actually am planning to write a new Redux core docs page with guidance and usage information on when and how to write memoized selectors. Until then, you can read my post Using Reselect Selectors for Encapsulation and Performance , which covers a lot of that material already.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • 1
    I would not suggest using `(state, id) => state.todos.entities[id];` not because of memoization but because it implements multiple things. 1) location of todos in state. 2) location of entities in todos and 3) shape of entities. If you compose selectors out of functions that do one thing you only need to change that one function when any of the 3 mentioned things change. If your function does more than one thing you probably end up implementing any of the previous 3 things multiple times and have to change multiple functions when something changes (duplicate implementation). – HMR Mar 04 '21 at 17:24
  • 1
    Here is the refactor of your example: `const selectTodos = state=>state.todos`, `const selectTodoEntities => createSelector([selectTodos],todos=>todos.entities)` and `const selectTodoById = (id) => createSelector([selectTodoEntities],entities=>entities[id])` A basic todo app does not show the benefit but what if the selectors implement complex business logic calculating something like the cost of a selected insurance policy? – HMR Mar 04 '21 at 17:27
  • 1
    Sure, you can always abstract the locations if you want to. On the other hand, in this case there really isn't any _benefit_ to doing so, as the single selector function does encapsulate both the "location" aspect and the "extraction" aspect from the point of view of the calling code. Also, you seem to have missed my point that there isn't any actual benefit to using Reselect for the "select item by ID" case, because there's no "derivation" - it's just a straight lookup and return. So, you're making this example more complicated than it needs to be. – markerikson Mar 04 '21 at 17:59
  • 1
    Thank you for your contributions to reselect, redux, react-redux and SO. The reason I bring this up is because the function does more than one thing and probably will be repeated by a `selectFinishedTasks` thus having duplicate implementation of the 3 things mentioned. A todo app is a contrived example and does not show why this could cause a maintenance problem but if you don't point it out a starting programmer will not learn until they actually create the problem and don't even know how they got there nor how to solve it. – HMR Mar 04 '21 at 18:07
  • 1
    Yes, I do get the point of "knowing to do it the right way", but it's also important to answer the actual question someone's asking, at the level of detail they're asking for :) Besides, I frequently see Reselect being used in cases where it shouldn't be, which is its own problem that I want to address, and so it's important to be clear on when memoization is actually needed. – markerikson Mar 04 '21 at 20:30
  • Hi markerikson, thank you for you answer. I get your point, but I don't quite get the why of the thing. Why shouldn't simple getters be memoized? As I stated in the second part of my post, because of the fact that memoized selectors are not recomputed, it returns the same reference to the value, and that avoids unnecessary render of React components. Thank you for the link. @HMR Your point is interesting, I'll make sure to bare that in mind – OTmn Mar 11 '21 at 18:00
  • @OTmn Your component should only select that what is needed so if your component needs a large part of the state but doesn't create a new object such as `const selectBigObject = state => state.bigObject` then your component will only re render when `state.bigObject` changes and that is what should happen, if not then you should not select the entire `bigObject` (only select what you need). The only reason to use reselect without calculating is for re using previously defined logic (see refactor suggestion) there is no calculation but prevents repeating logic (path to and shape of todo items) – HMR Mar 11 '21 at 18:23
  • 1
    Memoization is only useful if there is _derived_ data, such as `todos.map(t => t.text)`. If you're just _returning_ a value, like `(todos, index) => todos[index]`, there's literally nothing to memoize. – markerikson Mar 11 '21 at 20:06