Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Reselect selectors inside a Redux reducer

Tags:

redux

reselect

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
  }
}
like image 847
burtyish Avatar asked Aug 07 '17 13:08

burtyish


People also ask

Can you use a selector in a reducer?

It's not typically possible to use selectors inside of reducers, because a slice reducer only has access to its own slice of the Redux state, and most selectors expect to be given the entire Redux root state as an argument.

How do selectors work in Redux?

A selector is a function that accepts Redux state as an argument and returns data that is derived from that state. Selectors can provide performance optimizations to your application and can also help you encapsulate your global state tree.

What is reselect in Redux?

A library for creating memoized "selector" functions. Commonly used with Redux, but usable with any plain JS immutable data as well. Selectors can compute derived data, allowing Redux to store the minimal possible state.


2 Answers

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);
like image 87
Yury Tarabanko Avatar answered Oct 01 '22 04:10

Yury Tarabanko


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.

like image 43
Wouter Florijn Avatar answered Oct 01 '22 03:10

Wouter Florijn