selectors icon indicating copy to clipboard operation
selectors copied to clipboard

example of reselect selectors

reselect selectors example

Example app that uses reselect to select data from store.

Created with create-react-app so yarn install and yarn start (or npm).

Selectors can be found in selectors.js

What is a selector

A selector is a function that gets data from state and maybe calculate new values from that value. It can be used in react-redux connect or react-redux useSelector.

For example; if you want to get people from state and it is in state.data.people you can write a function like so: state => state.data.people. The problem with this is that you may at some point need to change the location of people or change the shape that people is stored in the state.

You can change the location when you descide that people should be in state.data.apiResult.data so you have to change all the functions. If you used reselect you only change one function: const selectPeople = state => state.data.apiResult.data. Or you can do even better and compose selectors.

Composing selectors

To solve this you can use reselect that allows you to compose selectors. Composing selectors is using a previous selector to create a new one.

Reselect has a function called createSelector that takes 2 arguments. The first is an array of functions and the second is a single function. It will return a function. Here is an example of a composed selector to get people:

const selectData = (state) => state.data;
const selectPeople = createSelector(
  [selectData],
  (data) => data.people
);

The first argument is an array of one of more functions, in the selectPeople example it is one function that selects data. The second argument is a function that gets the returned values of the array of functions from the first argument so in this case it gets state.data. The last function then selects people from data and returns that.

The implementation of where state.data is located is only defined once, if you change it later to state.data.apiResult you only need to modify one function.

Here is another example with multiple selectors where it gets the firstName and lastName of the first person in people:

const selectData = (state) => state.data;
const selectPeople = createSelector(
  [selectData],
  (data) => data.people
);
const selectFirstPerson = createSelector(
  [selectPeople],
  (people) => people[0]
);
const selectFirstName = createSelector(
  [selectFirstPerson],
  (person) => person.firstName
);
const selectLastName = createSelector(
  [selectFirstPerson],
  (person) => person.lastName
);
const selectFirstlastName = createSelector(
  [selectFirstName, selectLastName],
  (firstName, lastName) => ({ firstName, lastName })
);
const name = selectFirstlastName({
  data: {
    //selectData will get state.data
    people: [
      //selectPeople will get state.data.people
      {
        //selectFirstPerson will get state.data.people[0]
        //selectFirstName uses selectFirstPerson and gets firstName from that person
        firstName: 'Ruby',
        //selectLastName uses selectFirstPerson and gets lastName from that person
        lastName: 'Rose',
      },
    ],
  },
}); //name is {firstName:"Ruby",lastName:"Rose"}

The first argument to createSelector creating selectFirstlastName is an array with selectFirstName and selectLastName. The second argument to createSelector is a function that takes 2 arguments the first is firstName that came from selectFirstName and the second argument is lastName that comes from selectLastName.

The problem with the code is that it only gets first person from the array, what if I want to get a person who's first name is Ben?

Curry

In order to make a selector that finds people whose first name is "Ben" or another name I need a selectPeopleByName that takes the state and a name as an argument. You can pass multiple arguments to a selector but I will use a curried function instead.

A curried function is a function that takes one or more arguments and returns a new function. Here is an example of a curried function named createMultiplier that takes multiplier and returns a function that takes a number and when you call that function with a number it will multiply that number with multiplier.

const createMultiplier = (multiplier) => (number) =>
  number * multiplier;
const timesTen = createMultiplier(10);
const timesThree = createMultiplier(3);

timesTen(3); //30
timesThree(3); //9
timesTen(timesThree(2) /** 2*3=6 */); //60

The multiplier argument is available in closure scope. You can say that the function returned (timesTen and timesThree) closes over the multiplier argument.

Parameterized selector

Lets create a curried selector named createSelectPeopleByFirstName that will get a first name and return a selector function that receives the state and returns an array of people where the first name is what you passed to createSelectPeopleByFirstName (a curried function that closes over firstName)

const selectPeople = (state) => state.people;
const createSelectPeopleByFirstName = (firstName) =>
  createSelector([selectPeople], (people) =>
    people.filter(
      (person) => person.firstName === firstName
    )
  );

const state = {
  people: [{ firstName: 'Ben' }, { firstName: 'Jerry' }],
};
const peopleNamedBen =
  createSelectPeopleByFirstName('Ben')(state);
const peopleNamedJerry =
  createSelectPeopleByFirstName('Jerry')(state);

Memoization

Functions created with createSelector are memoized, this means that when the functions in the array passed as the first argument return the same value as when you called it last time the function as the second argument is not called and the value that was returned last time is returned instead.

Here is an example that is not memoized:

const state = { firstName: 'Ben', lastName: 'Stiller' };
const selectPersonFormatted = (state) => ({
  fullName: `${state.firstName} ${state.lastName}`, //returns new object every time
});
const one = selectPersonFormatted(state);
const two = selectPersonFormatted(state);
//this is false because selectPersonFormatted creates a new object every
//  time it's called
console.log(one === two);

When we use createSelector then not only do we split up how to get fist and last name but we also get memoized result:

const state = { firstName: 'Ben', lastName: 'Stiller' };
const selectFirstName = (state) => state.firstName;
const selectLastName = (state) => state.lastName;
const selectPersonFormatted = createSelector(
  [selectFirstName, selectLastName],
  //if firstName and lastName didn't change from what
  //  it was in the last call then the next function isn't
  //  called and previous value is returned instead
  (firstName, lastName) => ({
    fullName: `${firstName} ${lastName}`,
  })
);
const one = selectPersonFormatted(state);
const two = selectPersonFormatted(state);
//this is true, selectPersonFormatted is memoized because selectFirstName
//  and selectLastName return the same value both times they are called
console.log(one === two);

Parameterized and memoized

The example showing memoized selector only takes state as an argument. In this part I'll show how to create a memoized selector that takes a parameter to be used in components. We want to select a person by id. There is a list of ids that List component renders as Items:

const List = ({ items }) => (
  <ul>
    {items.map((item) => (
      <Item key={item.id} id={item.id} />
    ))}
  </ul>
);

The Item component gets a prop named id and will use that to select the item from state, here is the selector for that:

const selectItems = (state) => state.items;
const createSelectItemById = (itemId) =>
  createSelector([selectItems], (items) => {
    const item = items.find(({ id }) => id === itemId);
    //if you just return the item here then memoization
    //  doesn't matter, it will always return the same
    //  item, you could still use useMemo in your
    //  component to skip the find code but if you
    //  return a new reference like below then I would
    //  certainly use useMemo in the component so Item
    //  components in the list won't needlessly re render
    //  Actual DOM re render may not happen depending
    //  on the jsx returned and the code properly using
    //  useCallback for handlers you may pass as jsx
    //  properties
    return {
      ...item,
      fullName: `${item.first} ${item.last}`,
    };
  });

Here is a how to use it wrong in the Item component:

const Item = ({ id }) => {
  const selectItemById = createSelectItemById(id);
  const item = useSelector(selectItemById);
};

Every time Item renders the selectItemById is created again and nothing is memoized because each render re creates the selector. To solve this we can use React.useMemo. Here is the correct way to use createSelectItemById.

const Item = ({ id }) => {
  //selectItemById is only re created when id changes
  const selectItemById = React.useMemo(
    () => createSelectItemById(id),
    [id]
  );
  const item = useSelector(selectItemById);
};

Now when List renders multiple Item component each Item component will have it's own selectItemById selector that is not re created unless the id prop changes.

Since the component only re renders when id changes or when the return value of selectItemById changes you can also do this by making the component a pure component:

const Item = React.memo(({ id }) => {
  const item = useSelector(createSelectItemById(id));
});

Performance

Having your results memoized means that when an action is dispatched that changes something in state that is not relevant to your component then your component won't re render. This can improve performance and prevent unneeded re renders but does cost a little as well. As you can see with the previous example; each item creates a selectItemById function and that function is a selector created with reselect so it's 2 memoized curried functions that take time to be created and take up memory.

If your component often re renders when it should not then memoization will help. But if your component re renders because it needs to (state that it is using has changed so it will render something different) then creating all these memoized curried functions does not do much since they are just re created every time.

In this sample application all the selectors used are here. There is an action dispatched when clicking on the "dispatch unrelated" link that will re create state without changing any values and you can see that when clicking on the link it does not cause unneeded renders.