Alligator.io
In Redux a selector is a piece of logic that gets a specific piece of state from the store. Additionally, a selector can compute data from a given state, allowing the store to keep only basic raw data. Selectors are usually used as part of the binding between the store and the container components. In React for example, the mapStateToProps
function that’s passed to React Redux’s connect
function is where selectors would be used to select the needed slices of state for a container component.
Selectors can be created without the help of a library like Reselect, but using Reselect carries a few benefits in terms of developer experience and performance:
createSelector
function are memoized. That’s a fancy word to mean that the function remembers the arguments passed-in the last time it was invoked and doesn’t recalculate if the arguments are the same. You can view it a little bit like caching.Just add the reselect
package to your project using npm or Yarn:
$ npm install reselect
# or, using Yarn:
$ yarn add reselect
With this, you can start creating selectors either in the same file as your reducers, or in their own separate file. Here for example we create some selectors for a simplistic todo app where we’ll like to select todo items that contain the string *“milk”* and those that also contain the string *“bread”*:
// ...
import { createSelector } from 'reselect';
const todoSelector = state => state.todo.todo;
export const todosWithMilk = createSelector([todoSelector], todos => {
return todos.filter(todo => todo.title.toLowerCase().includes('milk'));
});
export const todosWithMilkAndBread = createSelector([todosWithMilk], todos => {
return todos.filter(todo => todo.title.toLowerCase().includes('bread'));
});
// ...
With this example, we have 3 selectors: todoSelector
, todosWithMilk
and todosWithMilkAndBread
. The first selector is known as an input selector. Input selectors are as simple as it gets, they take in the state as an argument and return a slice of that state. Input selectors should always be pure functions.
The 2nd and 3rd selectors in our example, todosWithMilk
and todosWithMilkAndBread
, are selectors created using Reselect’s createSelector
function. createSelector
expects 2 arguments: an array of input selector(s) as the 1st argument and a function as the 2nd argument, known as the transform function. The transform function receives the result(s) from the input selector(s) as arguments and should return the desired computed piece of state.
Selector composition is also demonstrated here, with the todosWithMilkAndBread
being composed on top of the todosWithMilk
selector. This helps keep your selector logic simple.
Note that you can also pass-in the input selectors to createSelector
as a list of arguments, no need for them to be in an array. As long as the last argument passed-in is the transform function.
Let’s make use of our todosWithMilk
and todosWithMilkAndBread
selectors in a Todos
container component:
import React from 'react';
import { connect } from 'react-redux';
import { deleteTodo } from '../actions';
import { todosWithMilk, todosWithMilkAndBread } from '../reducers';
function Todos({
withMilk,
withMilkAndBread,
error,
loading
}) {
return (
<div>
{/* ... */}
</div>
);
}
const mapStateToProps = state => {
return {
withMilk: todosWithMilk(state),
withMilkAndBread: todosWithMilkAndBread(state),
error: state.todo.error,
loading: state.todo.loading
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteTodo(id));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Todos);
As you can see, it’s as easy as calling our selectors with the state object passed-in. Behind the scenes our selectors will verify if it was called with the same argument values the last time it ran, and it won’t recompute the state if that’s the case.
Our previous example is rather simplistic and in a real app you’ll instead most often have multiple input selectors and compute the desired slice of state based on the result of these input selectors. For example, in a more realistic todo app, you could have an input field to filter down the visible todo items that contain a specified search term. This could be accomplished with selectors that look a little bit like the following:
// ...
import { createSelector } from 'reselect';
const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;
export const filteredTodos = createSelector(
[todoSelector, searchTermSelector],
(todos, searchTerm) => {
return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
}
);
// ...
And with this, we can use the filteredTodos
selectors to get all the todos if there’s no searchTerm
set in the state, or a filtered list otherwise.
👷 I hope that with this you’ll feel comfortable to start using selectors as part of your Redux apps and reap the performance benefits. You can refer to the project’s readme for a more in-depth look at the API and to learn about more advanced use cases like accessing component props in selectors.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!