Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Redux Reselect memoization work?

I am trying to integrate reselect into my current app and as always , first i begin to read documentation and then if it needed , another recources.I couldn't understand one special part of documentation and also couldn't find recources which would explain in a more clear way.Now i am here to get some clear explanation . So it says in documentation `

import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <VisibleTodoList listId="1" />
    <VisibleTodoList listId="2" />
    <VisibleTodoList listId="3" />
  </div>
)

Using the getVisibleTodos selector with multiple instances of the VisibleTodoList container will not correctly memoize:

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibleTodos } from '../selectors'

const mapStateToProps = (state, props) => {
  return {
    // WARNING: THE FOLLOWING SELECTOR DOES NOT CORRECTLY MEMOIZE
    todos: getVisibleTodos(state, props)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

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

export default VisibleTodoList

A selector created with createSelector has a cache size of 1 and only returns the cached value when its set of arguments is the same as its previous set of arguments. If we alternate between rendering <VisibleTodoList listId="1" /> and <VisibleTodoList listId="2" />, the shared selector will alternate between receiving {listId: 1} and {listId: 2} as its props argument. This will cause the arguments to be different on each call, so the selector will always recompute instead of returning the cached value.

Pay attention to last sentence . Why to return cached value if we pass different ids and it should return us different values depends on ids ?

like image 974
Norayr Ghukasyan Avatar asked Oct 02 '18 11:10

Norayr Ghukasyan


2 Answers

So we have this selector getting state for our VisibleTodoList component:

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

If we render the component:

return (
    <div>
        <VisibleTodoList listId="1" />
    </div>
)

Then, the selector gets called like: getVisibleTodos(state, { listId: 1 }), and the selector stores (memoizes) the result (todo list 1 object) in memory.

If we render the component twice with the same props:

return (
    <div>
        <VisibleTodoList listId="1" />
        <VisibleTodoList listId="1" />
    </div>
)
  1. the selector gets called and result gets memoized

  2. the selector gets called a second time, and it sees that { listId: 1 } is the same prop arguments as the first time, so it just returns the memoized value.

If we render the component twice with different props:

return (
    <div>
        <VisibleTodoList listId="1" />
        <VisibleTodoList listId="2" />
    </div>
)
  1. the selector gets called and the result gets memoized

  2. the selector gets called a second time, and it sees that { listId: 2 } is not the same props args as the first time { listId: 1 }, so it recalculates and memoizes the new result (todo list 2 object) in memory (overwriting the previous memoization).

If we want each component to get its own memoization, each component instance must have its own selector instance.

For example:

// selector
const makeGetVisibleTodos = () => createSelector(
    // ... get the visible todos
);

// each has their own memoization space:
const foo = makeGetVisibleTodos(); // this is an instance
const bar = makeGetVisibleTodos(); // this is a separate instance

So applying it to the component:

// component
const mapStateToProps = () => {
    const getVisibleTodos = makeGetVisibleTodos(); // this instance get bound to the component instance

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

Now, If we render the component twice with different props:

return (
    <div>
        <VisibleTodoList listId="1" />
        <VisibleTodoList listId="2" />
    </div>
)
  1. with <VisibleTodoList listId="1" /> the first instance of the selector gets called and the result gets memoized

  2. with <VisibleTodoList listId="2" /> a different instance of the selector gets called and the result gets memoized

like image 153
brietsparks Avatar answered Sep 24 '22 13:09

brietsparks


No, it does not return wrong value. documentation just says memoization will not work at all for that case. To make it work(in meaning "save some resources and avoid repeating the same calculation") you need.

Actually docs says(last sentence in section you have quoted):

We’ll see how to overcome this limitation in the next section.

And next section Sharing Selectors with Props Across Multiple Component Instances says

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

Also for sure you may increase memoization size to be more than 1.

like image 42
skyboyer Avatar answered Sep 22 '22 13:09

skyboyer