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
}
}
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.
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.
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With