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 ?

Tal Avissar
  • 10,088
  • 6
  • 45
  • 70
Norayr Ghukasyan
  • 1,298
  • 1
  • 14
  • 28

2 Answers2

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

brietsparks
  • 4,776
  • 8
  • 35
  • 69
  • This makes sense ... Thanks for deep explanation – Norayr Ghukasyan Oct 31 '18 at 17:30
  • 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? – james emanon Dec 23 '19 at 22:52
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.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • 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 and , 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? – Norayr Ghukasyan Oct 02 '18 at 11:58
  • it does not return cached value. it recomputes. because of `id` are different. – skyboyer Oct 02 '18 at 12:09
  • That's right and that's the point of my misunderstaning . Yes it recomputes , because the `ID`s are different for retrieving different data from store based on that `ID`s . 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 ? – Norayr Ghukasyan Oct 02 '18 at 12:13
  • 1
    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 useless – skyboyer Oct 02 '18 at 12:19
  • Sorry , but you cant catch my mind . I know the purpose of memoization . In this case they pass different `ID`s 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 value – Norayr Ghukasyan Oct 02 '18 at 13:09
  • 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. – skyboyer Oct 02 '18 at 13:15