How is state passed to selectors in a react-redux app?

krikey picture krikey · Jan 10, 2017 · Viewed 9.8k times · Source

I came across an example, where the mapStateToProps function is using memoization. I was just wondering how the "state" parameter is passed onto memoized selectors. After looking at the docs for reselect as well as redux, it seems that the mapStateToProps can return a function which accepts state as its argument, and the connect decorator might be the one passing the state to it but am not sure. Can someone please shed some light?

views/tracklist/index.js

const mapStateToProps = createSelector(
  getBrowserMedia,
  getPlayerIsPlaying,
  getPlayerTrackId,
  getCurrentTracklist,
  getTracksForCurrentTracklist,
  (media, isPlaying, playerTrackId, tracklist, tracks) => ({
    displayLoadingIndicator: tracklist.isPending || tracklist.hasNextPage,
    isMediaLarge: !!media.large,
    isPlaying,
    pause: audio.pause,
    pauseInfiniteScroll: tracklist.isPending || !tracklist.hasNextPage,
    play: audio.play,
    selectedTrackId: playerTrackId,
    tracklistId: tracklist.id,
    tracks
  })
);

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Tracklist);

core/tracklists/selectors.js

export function getCurrentTracklist(state) {
//  console.log(state);
  let tracklists = getTracklists(state);
  return tracklists.get(tracklists.get('currentTracklistId'));
}

export const getTracksForCurrentTracklist = createSelector(
  getCurrentPage,
  getCurrentTrackIds,
  getTracks,
  (currentPage, trackIds, tracks) => {
    return trackIds
      .slice(0, currentPage * TRACKS_PER_PAGE)
      .map(id => tracks.get(id));
  }
);

Answer

therewillbecode picture therewillbecode · Jan 10, 2017

Overview of how state is passed down to a selector when we use the Connect component from react-redux

What is a selector?

A selector extracts a subset of data from a source.

Let us think of the Redux store as our 'front end database'. For the purposeIn a database if you want to extract a subset of the total data you execute a query. In a similar fashion selectors are our queries to the Redux store.

In the simplest case, a selector could just return the state of the entire store.

The reselect docs give us three great reasons to use selectors

  • Selectors can compute derived data, allowing Redux to store the minimal possible state.
  • Selectors are efficient. A selector is not recomputed unless one of its arguments change.
  • Selectors are composable. They can be used as input to other selectors.

What is a higher order component?

A higher-order component is a function that takes an existing component and returns a new component.

Connect is a higher order component that be given a selector

Taken from this brilliant gist which gives a good explanation of connect.

connect() is a function that injects Redux-related props into your component.

Connect is a higher order component that makes our React component know about the Redux store. When we call connect we can pass mapStateToProps and mapDispatchToProps. These functions define the way in which our new component will be connected to the redux store.

We can give it access to state by passing a mapStateToProps function as an argument.

We can also bind action creators to store.dispatch through mapDispatchToProps. The advantage of this is that we don't need to pass down the entire store in order for a component to have access to store.dispatch so that the component can dispatch its own Redux actions.

The mapStateToProps function we pass to Connect is a selector

From the react-redux docs

The mapStateToProps function takes a single argument of the entire Redux store’s state and returns an object to be passed as props. It is often called a selector.

Think of the object that is returned by mapStateToProps as the result of our query to the Redux store. The resulting

The mapStateToProps function should normally return a plain object.

The result of calling mapStateToProps will normally be a plain object representing the data we extracted from the redux store.

The higher order Connect component allows us to extend the functionality of an existing component by merging in the data from this new object with the component's existing props.

Since selectors are just functions we can connect them to the Redux store using the connect component as well.

However in some cases we can return a function. Why would we do this?

By returing a function in mapStateToProps we can hijack the rendering cycle of components and optimise performance

In advanced scenarios where you need more control over the rendering performance, mapStateToProps() can also return a function. In this case, that function will be used as mapStateToProps() for a particular component instance. This allows you to do per-instance memoization.

By passing the mapStateToProps function as an argument to our higher order component our connected component will be updated anytime the some state has changed in the Redux store.

If these updates happen very frequently or the state tree is large then the reselect library is useful as it allows us to use memoized selectors.

This fancy word means that results of selector calls are stored in case they need to be retrieved again.

So if mapStatesToProps returned a plain object instead of a function then whenever our store state changed then we would have new props for our component.???

Connecting selectors to the store

If you are using React Redux, you can call selectors as regular functions inside mapStateToProps():

import { getVisibleTodos } from '../selectors'

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

Sharing Selectors Across Multiple Components

We can give reselect selectors props just like components when using the reselect library. This allows us to share selectors across multiple components.

Say we have multiple toDo lists each with their own Id. We would still use the same getVisibleTodos selector for each toDo list instance but just pass a different id as a prop.

However the issue with this is that createSelector only returns the cached value when its set of arguments is the same as its previous set of arguments.

The reselect docs point out that we can overcome this limitation by returning a function inside mapStateToProps:

In order to share a selector across multiple components and retain memoization, each instance of the component needs its own private copy of the selector. 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.

By returning a function inside mapStateToProps we can overcome this limitation and memoization will work correctly.

For a more detailed explanation see this