How to use Reselect selectors inside a Redux reducer
Asked Answered
B

2

20

My app already has a large collection of selectors used by the various container objects. These are great for accessing different parts of the state and make refactoring the state much easier.

Now I want to use my selectors inside some of my reducer functions. The problem is that inside the reducer, the state parameter refers to a specific slice of the state, whereas the selector functions expect to be called with the state root object.

Contrived Example:

/* Selectors */
const getTodos = state => state.todos;

const getUncompletedTodos = createSelector(
    [ getTodos ],
    todos => todos.filter(t => !t.completed)
);

/* Reducer */
const todosReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ];
    case 'REMOVE_COMPLETED_TODOS':
      return getUncompletedTodos(state); // <-- this won't work
  }
}
Buskirk answered 7/8, 2017 at 13:7 Comment(0)
M
22

You selector works from root state object.

To fake this you could do

 return getUncompletedTodos({todos: state});

But IMHO a better idea would be to reuse filtering function

/* Selectors */
const getTodos = state => state.todos;

const filterCompleted = todos => todos.filter(t => !t.completed)

const getUncompletedTodos = createSelector(
    [ getTodos ],
    filterCompleted
);

// inside reducer
case 'REMOVE_COMPLETED_TODOS':
    return filterCompleted(state);
Maxantia answered 7/8, 2017 at 13:14 Comment(5)
Why would you not want to take advantage of the memoization in the getUncompletedTodos-selector?Chronologist
@Chronologist You are calling reducer to mutate the state. Memoization here is like always missing cache.Maxantia
How would you suggest approaching a more complex selector? Let's say I want to access a specific item within a specific todo (ex: getCurrentTodo, getTaskListInCurrentTodo). In this case, using your second suggestion would require you to call both functions to access the item. In other words, you can't reuse combinations of selectors without using the 'faking' method you mention?Sluiter
@Sluiter hard to say w/o seeing your code. Generally speaking reselect is a simple memoization that works if: the function is subsequently called multiple times with the same arguments and is "heavy enough" to overweight additional operations added by the lib. If your case matches the criteria it might benefit from memoization.Maxantia
I don't think this is a good idea. Quoted: "You are calling reducer to mutate the state. Memoization here is like always missing cache. " It is NOT true. Because at the moment I invoke the selector, the mutation has NOT been performed. So I should be able to take advantage of the memoization.Lifeguard
B
1

The answer by Yury works, but doesn't take advantage of memoization (see comments). If you want that, the solution would be to write the selector only for the slice of the state that it needs.

The selector would become:

const getUncompletedTodos = createSelector(
    [todos => todos], // Not sure if there's a way to skip this redundancy and still take advantage of memoization with reselect.
    todos => todos.filter(t => !t.completed)
);

In the reducer, you would simply use it like this:

case 'REMOVE_COMPLETED_TODOS':
    return getUncompletedTodos(state);

However, when using the selector on the root state somewhere else, you use it like this:

getUncompletedTodos(state.todos)

The only downside I see is that you would have to remember to call the selector with the right part of the state, though of course if you're using TypeScript properly, it will remind you of this.

Bluegreen answered 15/3, 2021 at 12:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.