How can I get ownProps using reselect on redux?

Sibelius Seraphini picture Sibelius Seraphini · Apr 4, 2016 · Viewed 12.2k times · Source

I want to create a selector with memoization using reselect based on some ownProps of mapStateToProps.

Answer

Seth picture Seth · May 24, 2016

You can do this by connecting the selector to a component using the connect method provided by react-redux, then passing the component props (ownProps) as the second argument to the selector.

container.js

import { connect } from 'react-redux';
import { getVisibleTodos } from './selectors';

...

const mapStateToProps = (state, props) => {
  return {
    todos: getVisibleTodos(state, props),
  };
};

const VisibleTodoList = connect(
  mapStateToProps,
)(TodoList);

export default VisibleTodoList;

You can then access those props in your selector

selectors.js

import { createSelector } from 'reselect';

const getVisibilityFilter = (state, props) =>
  state.todoLists[props.listId].visibilityFilter;

const getTodos = (state, props) =>
  state.todoLists[props.listId].todos;

const getVisibleTodos = createSelector(
  ...
);

export default getVisibleTodos;

However, this will not memoize correctly if you have multiple instances of the component you're passing props from. In that case, the selector would receive a different props argument each time, so it would always recompute instead of returning a cached value.

To share a selector across multiple components while passing in props and retaining memoization, each instance of the component needs its own private copy of the selector.

You can do this by creating a function that returns a new copy of the selector each time it's called.

selectors.js

import { createSelector } from 'reselect';

const getVisibilityFilter = (state, props) =>
  state.todoLists[props.listId].visibilityFilter;

const getTodos = (state, props) =>
  state.todoLists[props.listId].todos;

const makeGetVisibleTodos = () => {
  return createSelector(
    ...
  );
}

export default makeGetVisibleTodos;

If the mapStateToProps argument supplied to connect returns a function instead of an object, it will be used to create an individual mapStateToProps function for each instance of the container.

With that in mind, you can create a function makeMapStateToProps that creates a new getVisibleTodos selector, and returns a mapStateToProps function that has exclusive access to the new selector:

import { connect } from 'react-redux';
import { makeGetVisibleTodos } from './selectors';

...

const makeMapStateToProps = () => {
  const getVisibleTodos = makeGetVisibleTodos();
  const mapStateToProps = (state, props) => {
    return {
      todos: getVisibleTodos(state, props),
    };
  };
  return mapStateToProps;
};

const VisibleTodoList = connect(
  makeMapStateToProps,
)(TodoList);

export default VisibleTodoList;

Now each instance of the VisibleTodosList container will get its own mapStateToProps function with a private getVisibleTodos selector. Memoization will now work correctly regardless of the render order of the containers.


This was adapted (blatently copied) from the Reselect documentation