How does Redux Reselect memoization work?
Asked Answered
S

2

7

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 ?

Scyros answered 2/10, 2018 at 11:35 Comment(0)
D
17

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

Daphne answered 30/10, 2018 at 18:41 Comment(2)
This makes sense ... Thanks for deep explanationScyros
Isn't it expensive to check against every prop to see if anything has changed than just doing the computation again? What if its a non-expensive item? Isn't the "check" to see if its memoized an expensive operation?Sussman
O
1

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.

Oliverolivera answered 2/10, 2018 at 11:51 Comment(6)
I've already read the whole documentation and i know there is a method to overcome the problem,but my quetsion isn't that.They said that` 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. Please pay attention to last sentence. Why the selector needs to return cached value when id-s are different?Scyros
it does not return cached value. it recomputes. because of id are different.Oliverolivera
That's right and that's the point of my misunderstaning . Yes it recomputes , because the IDs are different for retrieving different data from store based on that IDs . So why they treat it as a problem ? But there is a problem! Using the getVisibleTodos selector with multiple instances of the VisibleTodoList container will not correctly memoize: So why they say that it's a problem ?Scyros
memorization should help to save resources by avoiding repeating complex/heavy computations. and in this case you need to do extra actions or memorization will be uselessOliverolivera
Sorry , but you cant catch my mind . I know the purpose of memoization . In this case they pass different IDs to retrieve different data , so it's normal that the selector will recompute , but they say it's a problem and it should return cached valueScyros
I see. But since 'A selector created with createSelector has a cache size of 1' caching will really rarely be helpful. Typically selector is called each time dispatch is called so cache size of 1 definitely makes sense and helps. But in case described(several components shares the same cache) it will not help because recomputing will be done almost every time.Oliverolivera

© 2022 - 2025 — McMap. All rights reserved.