Советы

Селекторы в Redux: useSelector

От автора: селекторы — это удобный способ «выбрать» состояние из магазина Redux. Хранилище Redux хранит всю логику и данные приложения; селекторы используются для выбора определенных частей состояния из хранилища. Если вы создаете приложения с использованием React и Redux, вы можете воспользоваться селекторами.

Как выглядят селекторы?

Прежде чем мы рассмотрим то, почему селекторы удобны и как их использовать, давайте посмотрим, как приложение выглядит без них. Я уже создал самый необходимый нам минимум — базовую настройку Redux. У нас есть данные rootreducer, которые в настоящее время содержат всю информацию о наших животных:

const initialState = {
data: [
{
name: «Milton»,
type: «cat»,
age: 4
},
{
name: «Sammy»,
type: «dog»,
age: 2
},
{
name: «Joseph»,
type: «turtle»,
age: 34
},
{
name: «Simon»,
type: «dog»,
age: 8
}
]
};

export default (state = initialState, action) => {
switch (action.type) {
default: {
return { …state };
}
}
};

export default (state = initialState, action) => {

rootreducer передается в хранилище, а хранилище передается в . Наконец, PetsContainer.js подключается к хранилищу, выбирает животных, находит всех собак в данных и отображает их на экране.

Хорошо, скажем, вы хотите выбрать всех собак из хранилища и отобразить их на экране, как бы вы поступили? Простейший подход будет выглядеть так:

Селекторы в Redux: useSelector

Выбор состояния хранилища, фильтрация и поиск всех собак.

Заметили что-нибудь странное? Мы выполняем фильтрацию внутри обратного вызова useSelector. Если бы мы делали это более одного раза, наше приложение резко замедлилось бы. Не говоря уже о том, что это не очень красивый код.

Если вы знакомы с Redux, вы знаете, что у нас есть редукторы для изменения и сохранения состояния, действия для запуска триггеров изменения состояния. Третья недостающая загадка здесь — селектор.

Селекторы прекрасно вписываются в картину, так как нам нужен эффективный способ получения данных из хранилища и их отображения.

Добавление библиотеки Reselect

Reselect — это простая библиотека «селекторов» для Redux (и не только).

Селекторы в Redux: useSelector

Вот почему имеет смысл использовать селекторы с приложениями React и Redux:

Селекторы могут вычислять производные данные, позволяя Redux сохранять минимально возможное состояние.

Селекторы эффективны. Селектор не пересчитывается, пока не изменится один из его аргументов.

Селекторы являются составными. Их можно использовать как вход для других селекторов.

Установка библиотеки Reselect

Установка библиотеки Reselect проста. Вы можете получить ее, установив, как и любой другой пакет NPM:

Теперь у нас есть библиотека, давайте создадим новый файл с именем selectors.js — это файл, в котором мы храним всю логику, касающуюся селекторов. Затем импортируйте метод createSelector из библиотеки Reselect:

import { createSelector } from «reselect»;

import { createSelector } from «reselect»;

Отлично! Теперь у нас есть доступ к методу createSelector.

Создание вашего первого селектора

Селекторы просты для понимания. createSelector принимает столько аргументов, сколько вам нужно, последний аргумент используется для выполнения фильтрации / выбора. Допустим, мы хотим выбрать всех собак, вот что мы передаем селекторам.

Передайте всех животных селектору в качестве первого аргумента, это зависимости.

Последний аргумент всегда является обратным вызовом с аргументами в качестве зависимостей, переданных ранее.

Вот как выглядит код:

import { createSelector } from «reselect»;

const selectAllPets = state => state.pets.data;

export const selectAllDogs = createSelector(
selectAllPets,
allPets => allPets
);

import { createSelector } from «reselect»;const selectAllPets = state => state.pets.data;export const selectAllDogs = createSelector(

Теперь, если мы вернемся к PetsContainer, мы можем импортировать селектор selectAllDogs и передать его в хук useSelector:

Селекторы в Redux: useSelector

Обратите внимание, что сейчас мы возвращаем всех животных. Это потому, что это именно то, что мы делаем в селекторе. Чтобы вместо этого вернуть всех собак, нам нужно применить логику фильтра внутри селектора:

Селекторы в Redux: useSelector

Обратите внимание, что сейчас мы получаем всех собак. Вместо того, чтобы выполнять фильтрацию внутри компонента, мы просто отображаем собак. Это отличный способ снизить нагрузку на компоненты. Помните, что компоненты React хороши для рендеринга разметки, все остальное должно быть сделано в другом месте, чтобы уменьшить количество спагетти и разделить задачи.

Мы даже можем передавать селекторы в качестве аргументов другим селекторам. Как мы можем выбрать всех собак и кошек из хранилища?

Селекторы в Redux: useSelector

  • Во-первых, мы получаем всех животных.
  • Как только у нас будут все животные, мы фильтруем их, чтобы найти всех собак и кошек.
  • Как только у нас выбраны все собаки и кошки, мы объединяем их в один селектор, передавая селекторы в качестве аргументов.

Селекторы в Redux: useSelector

Вот и все — полезные и простые компоненты React. Помните, что результаты селектора запоминаются, то есть результаты сохраняются в памяти и всегда возвращают один и тот же результат, если только результаты не изменились. Это значительно увеличивает производительность.

  1. Indrek Lasn
  2. Источник: //medium.com
  3. Редакция: Команда webformyself.

How to connect Redux Store components with useSelector & React.memo

Here we’ll show you an intricate guide on how to correctly apply useSelector to efficiently render components, how to set up the custom compare function to avoid rendering and how to memoize components, helping performance by reducing the number of times the logic inside the selector is triggered

Written by: 

Albert Vinyals,
iOS Developer at Nuvolar

This article aims to describe the best options the Mobile team at Nuvolar considers when using functional components that connect to the Redux Store thanks to the useSelector hook.

After previous discussions, the team decided to set a goal: to write this “Best Practices” article to make sure that, from now on, we all use the same, correct and consistent, approach, which we are explaining in detail here.

1. useSelector: The hook which allows you to extract data from the Redux Store

The selector is approximately equivalent to mapStateToProps and will be called when  the store state changes. 

Please note that useSelector will do a reference comparison (===) of the previous selector result.

If the result is different the component will be re-rendered. For primitive values, this will have no major impact but please take this into consideration when working with objects (including  arrays).

useSelector is called every time an action is dispatched to the store. 

1a. Destructuring: Best way to avoid unnecessary rendering

  • As mentioned previously, the useSelector will use strict reference equality to decide whether a render has to be triggered or not. Imagine the following scenario:
  • Селекторы в Redux: useSelector
  • If the user’s state object reference changes (even maintaining the values for name and photo keys) the component will re-render (it is comparing 2 different objects references).
  • In order to avoid this unnecessary render please consider the following approach:
  • Селекторы в Redux: useSelector

This will only render if the values for the keys name and photo change. useSelector will be comparing primitives in this case, so only if the string value is different the render will be triggered.

There is no inconvenience in creating a new useSelector for each value. This is what the documentation states:

You may call useSelector() multiple times within a single function component. Each call to useSelector() creates an individual subscription to the Redux Store. Because of the React update batching behavior used in React Redux v7, a dispatched action that causes multiple useSelector()s in the same component to return new values should only result in a single re-render.

1b. Comparing objects

In case you have the need to compare objects and you can not use values instead, there are still 2 options one can use.

Remember, the main goal is to avoid re-rendering when it is not strictly necessary (because a value used for rendering purposes changed).

You can specify a function that will be used to compare the result of the selector. Feel free to implement your own or to use the already existing shallowEqual.

Селекторы в Redux: useSelector

Please bear in mind that this may hit the performance since shallowEqual may be heavy in terms of processing (depending on the object shape).

Use reselect or a similar library that returns multiple values in one object, but only returns a new object when one of the values has changed. On top of that, this approach helps performance by reducing the number of times the logic inside the selector is triggered.

Читайте также:  Медиаискусство (Media Art): что это такое, примеры, виды

Селекторы в Redux: useSelector

2. React.memo: helping memoize with parent components

UseSelector and UseDispatch: A Guide to React-Redux Hooks

React Redux offers a set of hooks as an alternative to the existing connect()higher-order component. These hooks allow you to connect to the Redux store and dispatch actions without having to wrap your components in connect().

This guide will cover how to implement the React-Redux hooks useSelector and useDispatch in your application.

useSelector and useDispatch are a set of hooks to use as alternatives to the existing connect() higher-order component. The equivalent of map state to props is useSelector.

It takes in a function argument that returns the part of the state that you want. The equivalent of map dispatch to props is useDispatch. We can invoke useDispatch and store it to a variable, dispatch.

Dispatch will work with the allActions imported from the actions folder.

More in Software Engineering10 iOS Development Tips for Swift and Xcode

Getting Started

Begin by installing the following in your app:

npm install redux
npm install react-redux

The examples in this guide will be referring to my repository, available here.

Here is a quick gif of the app’s functionality:

There are two separate states, one for keeping track of the counter and one for keeping track of a logged-in user. We will have separate files to handle each state.

Create an Actions and Reducers Folder

Селекторы в Redux: useSelectorAn actions and reducers folder. Image: Screenshot

Actions

Let’s begin by defining the actions for the counter in counterActions.js. A couple of methods are necessary: increment and decrement. We will export these two methods in an object.

const increment = () => {
return {
type: «INCREMENT»
}
}

const decrement = () => {
return {
type: «DECREMENT»
}
}

export default {
increment,
decrement
}

Similarly, let’s define the actions for the current user in userActions.js, which will also have two methods, setUser and logOut, that will be exported in an object.

const setUser = (userObj) => {
return {
type: «SET_USER»,
payload: userObj
}
}

const logOut = () => {
return {
type: «LOG_OUT»
}
}

export default {
setUser,
logOut
}

To be organized, we import these two files under one location, the index.jsfile, within the actions folder. We create a variable allActions and set it to an object that contains the imported actions that will be exported.

import counterActions from './counterActions'
import userActions from './userActions'

const allActions = {
counterActions,
userActions
}

export default allActions

Reducers

Similar to the approach taken for the actions file structure, we create a separate reducers folder to hold the user and the counter reducers. Let’s start with the counter reducer, counter.js.

A reducer function takes in two arguments, the state and the action. The state doesn’t necessarily have to be set to an object. In this case, the default value of the state is set to an integer.

Using the React-Redux useSelector Hook

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at the new hooks React-Redux Hooks API.

The hooks API consists of the useSelector , useDispatch , and useStore hooks.

The useSelector hook takes a selector function to select data from the store and another function equalityFn to compare them before returning the results and determine when to render if the data from the previous and current state are different.

The call takes the following format:

const result : any = useSelector(selector : Function, equalityFn? : Function)

It’s equivalent to mapStateToProps in connect. The selector will be called with the entire Redux store state as its only argument.

The selector function may return any value as a result, not just an object. The return value of the selector will be used as the return value of the useSelector hook.

When an action is dispatched, useSelector will do a shallow comparison of the previous selector result value and the current one.

The selector function doesn’t receive an ownProps argument. Props can be used through closure or by using the curried selector.

We may call useSelector multiple times within a single function component. Each call creates an individual subscription to the Redux store. React update batching behavior will cause multiple useSelector s in the same component to return new value should only return in a single re-render.

Equality Comparisons and Updates

The provided selector function will be called and its result will be returned from the useSelector hook. A cached result may be returned if the selector is run and returns the same result.

useSelector only forces a re-render if the selector result appears to be different than the last result.

This is different from connect , which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed.

If we want to retrieve multiple values from the store, we can call useSelector multiple times with each call returning a single field value, use Reselect or a similar library to create a memoized selector that returns multiple values in an object but returns a new object when one of the values is changed.

We can also use the shallowEqual function from React-Redux as the equalityFn argument to useSelector .

For example, we can use useSelector as follows:

import React from «react»;
import ReactDOM from «react-dom»;
import { Provider, useSelector, useDispatch } from «react-redux»;
import { createStore } from «redux»;
function count(state = 0, action) {
switch (action.type) {
case «INCREMENT»:
return state + 1;
case «DECREMENT»:
return state — 1;
default:
return state;
}
}

const store = createStore(count);

function App() {
const count = useSelector(state => state);
const dispatch = useDispatch();

return (

dispatch({ type: «INCREMENT» })}>Increment
dispatch({ type: «DECREMENT» })}>Decrement

{count}

);
}

const rootElement = document.getElementById(«root»);
ReactDOM.render(

,
rootElement
);

In the code above, we created the store as usual. Then in the App component, we used the useSelector hook to get the count by passing in the state => state function.

Then we get the dispatch function to useDispatch .

We can use the second argument of the useSelect hook as follows:

import React from «react»;
import ReactDOM from «react-dom»;
import { Provider, useSelector, useDispatch, shallowEqual } from «react-redux»;
import { createStore } from «redux»;
function count(state = 0, action) {
switch (action.type) {
case «INCREMENT»:
return state + 1;
case «DECREMENT»:
return state — 1;
default:
return state;
}
}
const store = createStore(count);
function App() {
const count = useSelector(state => state, shallowEqual);
const dispatch = useDispatch();
return (

dispatch({ type: «INCREMENT» })}>Increment
dispatch({ type: «DECREMENT» })}>Decrement

{count}

);
}
const rootElement = document.getElementById(«root»);
ReactDOM.render(

,
rootElement
);

React-Redux has a shallowEqual function to compare data to determine when to update data. We can also define our own function to compare the previous and current state to determine updates.

Conclusion

We can use the useSelector hook to get the data from the Redux store in a React component.

It takes 2 arguments. The first argument is a function that returns the state, and the second argument is a function that checks if the previous and current state are equal to determine when to update.

Introduction React-Redux using Hooks (useSelector && useDispatch)

Before reading this article you should know about React & Redux, its working.

This article is about React-Redux Hooks. We will go through below main points in this article:

* Hooks for Redux.

* How to use useDispatch Hook.

* How to use useSelector Hook.

1. Hooks for Redux

Before Hooks, we always used a connect() which is a higher-order component and wrapper to our component, connect() read values from the Redux store.

Читайте также:  Мультиязычность на Typescript и React

connect() takes two arguments, both optional:

  1. mapStateToProps
  2. mapDispatchToProps

called every time the store state changes. It receives the entire store state and should return an object of data this component needs.

This parameter can either be a function, or an object. If it’s a function, it will be called once on component creation. It will receive dispatch as an argument and should return an object full of functions that use dispatch to dispatch actions.

more about connect()

Let's move towards react-redux hooks. React-Redux now offers a set of hook APIs as an alternative to existing connect() Higher-Order Component. These APIs allow you to subscribe to the Redux store and dispatch actions, without having to wrap your components in connect(). By using the Hook API with Function components, components are kept small and the code remains clean.

Hooks:

2. useDispatch():

useDispatch() hook is equivalent of mapDispatchToProps.

We will invoke useDispatch and store it to a variable, dispatch. This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.
And we dispatch it by calling dispatch passing in the return value from the action creator.

How to use

Below is the small component where using useDispatch and useSelector

import React from «react»;
//import useDispatch from react-redux
import { useDispatch} from «react-redux»;
//these are actions define in redux>actions folder
import { updateFirstName } from «../redux/actions»;

const Form = () => {

const dispatch = useDispatch();

const handleFirstName = () => {
//dispatching the action
dispatch(updateFirstName(«Jason»));
};

return (

Update First
Name

);
};

export default Form;

complete code in GITHUB redux-hooks

3.useSelector():

useSelector() hook is equivalent of mapStateToProps

useSelector is a function that takes the current state as an argument and returns whatever data you want from it and it allows you to store the return values inside a variable within the scope of you functional components instead of passing down as props

import React from «react»;
import { useDispatch, useSelector } from «react-redux»;
import { updateFirstName } from «../redux/actions»;

const Form = () => {
const dispatch = useDispatch();
const nameObj = useSelector((state) => state.nameReducer);
const { firstName } = nameObj;
const handleFirstName = () => {
dispatch(updateFirstName(«Jason»));
};

return (

First Name : {firstName}
Update First Name

Last Name : {lastName}

Update First Name

);
};

export default Form;

complete code in GITHUB redux-hooks

useStore():

useStore() hook returns a reference to the same Redux store that was passed into Provider component.

This hook should probably not be used frequently. Prefer useSelector() as your primary choice. However, this may be useful for less common scenarios that do require access to the store, such as replacing reducers.

import React from 'react'
import { useStore } from 'react-redux'

export const ExampleComponent = ({ value }) => {
const store = useStore()

// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return {store.getState().obj.name}
}

  • complete code in GITHUB redux-hooks
  • If you wanna learn more about useDispatch and useSelector here it's official link React Redux Hooks
  • Further improvement, suggestion or help. Welcome 🙂

Frontender`s Spectre

Всем привет!

Redux вроде как изучили вдоль и поперек, но от этого он не стал идеальным. В статье мы детально разберем один из самых неприятных механизмов в Redux, которым многие стреляют себе в ногу. Собственно, этот механизм мы и изучим в статье (данная статья является расшифровкой этого видео):

Скрытная особенность useSelector

И так давайте начнем с общей схемы.

Допустим, у нас есть компонент Cars со списком машин. Чтобы получить этот список машин, мы используем useSelector с селектором getCars, который, в свою очередь, идет в Redux за необходимыми нам данными. И в итоге данные начинают вытягиваться, и пользователь наконец видит список машин у себя на странице:

Далее, допустим, что в этом компоненте мы вызвали action с добавлением новой машины в список машин. Собственно этот action и обновляет Redux store, чтобы добавить новую машину в список:

Далее, как мы знаем, Redux store должен обновить наш компонент. Но вопрос, как именно он это делает с технической стороны?

Работает это следующим образом: useSelector не просто считывает данные из Redux store при любом обновлении компонента, он также еще неявно подписывается на любые изменения Redux store (бежевые черточки на рисунке).

И когда интересующее нас значение в Redux store изменяется, useSelector еще и заставляет весь текущий компонент пере рисоваться.

В итоге после dispatch(addCar(newCar)), соответствующий reducer обновит наш Redux store.

И благодаря тому, что useSelector подписан на изменения в Redux store мы получаем желанный рендер компонента с новым значением cars, где уже в списке присутствует только что добавленная машина:

Получается, что useSelector не так прост, как кажется. Это не просто метод для получения данных из Redux store. На нем лежит куда больше ответственности, чем кажется на первый взгляд.

Масштабируем ситуацию

Чтобы понять всю ответственность, которая лежит на useSelector, давайте немного масштабируем пример. Допустим, у нас есть страничка, на которой отображен текущий пользователь. Ниже мы видим компонент со списком тех самых машин, а после него пусть еще будет список дилеров, у которых мы можем приобрести новое авто:

Каждый из этих компонентов нуждается в данных из Redux. Это значит, что у них всех используется useSelector. Только каждый компонент тянет свои данные.

Компонент с текущим пользователем использует селектор getCurrentUser, компонент с машинами, соответственно, тянет список машин с помощью getCars. И компонент с дилерами, конечно же, тянет список дилеров с помощью getDealers.

На что хотелось бы обратить внимание: все эти селекторы тянут информацию из одного и того же Redux store:

Вроде с начальными условиями разобрались.

Давайте теперь представим, что мы вызвали тот самый action с добавлением новой машины.

Вследствие чего Redux store должен обновиться и, соответственно, отправить всем useSelector сигнал, мол, проверьте, нужно обновить ваши компоненты или нет.

И, конечно, мы ожидаем, что useSelector компонента с машинами единственный обновится в данной ситуации. Это звучит логично, потому что мы добавили машину, а не обновили текущего пользователя или добавили дилера

Звучит крайне просто и удобно, но у этого всего есть одно узкое горлышко – useSelector, а именно механизм внутри него, который принимает решения, должен обновиться компонент или нет.

Этот механизм настолько хрупкий, что я неоднократно видел, как на разных проектах вместо одного компонента Cars обновляются на любой чих сразу все компоненты.

И неважно, добавляли мы машину или редактировали текущего пользователя.

Просто задумайтесь, насколько велика цена непонимания работы этого механизма, если у вас вся страница зависит от Redux данных.

Неправильная работа с этим механизмом может заставить перерисовывать абсолютно все приложение, на абсолютно любой action. А actions у вас может быть огромное количество.

В таком приложении о хорошем быстродействии приложения можно только молиться на выносливость устройств пользователей.

Изучаем узкое горлышко

Самое время рассмотреть в деталях, что из себя представляет тот самый механизм. Покопавшись в исходниках, я бы акцентировал внимание на следующем месте. Начнем с 70 и 71 строк:

// We may be able to reuse the previous invocation's result.
const prevSnapshot: Snapshot = (memoizedSnapshot: any);
const prevSelection: Selection = (memoizedSelection: any);

Здесь мы создаем переменную prevSnapshot и ниже prevSelection. Что означают слова prev, snapshot и selection в переводе на знакомые нам слова. Snapshot называют то, что мы называем state. То есть это состояние всего Redux store, из которого мы потом достаем текущего пользователя или список машин и так далее.

const getCars = (state) => state.cars
const getCurrentUser = (state) => state.currentUser

Читайте также:  Перехожу в продакт-менеджмент: как составить резюме новичку?

А selection обозначает как раз таки интересующие нас данные, те самые машины или текущий пользователь. Те данные, которые запрашивает наш селектор.

Далее о приставке prev и еще увидим приставку next. Они означают следующее. Допустим, у нас есть список из 2 машин. И мы решили туда добавить еще одну машину. Соответственно, prev состояние, это когда машины еще 2, а next состояние – это когда машин уже 3. То есть это до изменения любой части Redux store и после изменения.

Чтобы вам было легче воспринимать исходники, я решил их переписать. Теперь код будет выглядеть со знакомыми для нас всех словами на примере со списком машин.

What is a selector?

If redux is our front-end database, selectors are reusable functions that let
us query our database. There are some rules that are required for selectors to
be useful:

  • Pure functions (deterministic: given the same inputs they will always return
    the same outputs)
  • Function signature: (state: State, props?: { [key: string]: any }) => any

Selectors ought to keep to the function signature above. We should also try to
avoid using any and instead type exactly when the function requires and returns.

  • 1const selectControlsByLineage = (
  • 2 state: State,
  • 3 props: { lineage: string },
  • 4): Control[] => {};

# What is reselect?

reselect is a third-party library that helps us build composable selectors as
well as dramatically improve the performance of our queries with memoization.
Memoization is a technique to cache the result of a function. Since selectors
must be pure functions, if the inputs are the same, then the output must also be
the same from the previous computation.

This is one of the main mechanisms we
use to improve performance of our application. We use selectors on every single
page within our front-end application and some of the queries we make to our
database can be complex. Determining the time complexity of a selector is a
crucial part of improving the performance of our application.

By leveraging
reselect, we sacrifice memory for CPU cycles.

I ask everyone reading this to please spend 10 minutes reading the reselect
docs. They do a great job explaining the API with plenty of examples on how to
use it.

# When should I use reselect?

When to use reselect is very context dependent. Since these are reusable
queries, they have the opportunity to save other developers a lot of time
building their own selectors.

When it comes to using reselect for performance
reasons, it’s always recommended to analyze the performance of a query before
and after using reselect.

Having said that, I use a very simple heuristic for
when to use reselect:

If the time complexity for the query is equal to or worse than linear time
O(n) then we should probably build the selector using createSelector.

# Setup

  1. 1interface ToDo {
  2. 2 id: string;
  3. 3 text: string;
  4. 4 completed: boolean;
  5. 5}
  6. 6
  7. 7interface State {
  8. 8 todos: { [key: string]: ToDo };
  9. 9}
  10. 10
  11. 11const state: State = {
  12. 12 todos: {
  13. 13 1: { id: 1, text: «learn what a selector is», completed: true },
  14. 14 2: { id: 2, text: «learn what reselect is», completed: true },
  15. 15 3: { id: 3, text: «learn when I should use reselect», completed: false },
  16. 16 4: { id: 4, text: «learn how to write selectors», completed: false },
  17. 17 },
  18. 18};

# Example 1

1const selectTodos = (state: State) => state.todos;

Should we use reselect for selectTodos? To answer this question we need to
understand the time complexity of this function. Accessing a property on an
object is O(1) which is faster than linear time. Therefore, we do not need to
use reselect.

# Example 2

  • 1const selectTodoById = (state: State, props: { id: string }) => {
  • 2 const todos = selectTodos(state);
  • 3 return todos[id];
  • 4};

Should we use reselect for selectTodoById? A hash lookup is O(1), so no we
should not use reselect in this case.

# Example 3

1const selectCompletedTodos = (state: State) => {

2 const todos = selectTodos(state);

3 return Object.values(todos).filter((todo) => todo.completed);

4};

Should we use reselect for selectCompletedTodos?
Object.values for v8 appears to be O(n)
and the filter operation on the lists of todos is also O(n). This operation
should be memoized since it requires linear time to complete.

How would we convert the above function to use createSelector?

  1. 1import { createSelector } from «reselect»;
  2. 2
  3. 3const selectTodosAsList = createSelector((todos) => Object.values(todos));
  4. 4const selectCompletedTodos = createSelector(
  5. 5 selectTodosAsList, // selector composition is critical!

6 (todoList) => todoList.filter((todo) => todo.completed),

7);

# createSelector limitation

It’s important to note that createSelector will only cache the last result. So
if the inputs keep changing then it will constantly recompute the query.

# Example

  • 1import { createSelector } from «reselect»;
  • 2
  • 3const selectTodosByText = createSelector(
  • 4 selectTodos,
  • 5 (state: State, props: { search: string }) => props.search,

6 (todos, search) => todos.filter((todo) => todo.text.includes(search)),

  1. 7);
  2. 8
  3. 9selectTodosByText(state, { search: «what» });
  4. 10// returns a cached result!
  5. 11selectTodosByText(state, { search: «what» });
  6. 12
  7. 13// recomputes because the input changed
  8. 14selectTodosByText(state, { search: «when» });
  9. 15// recomputes beacuse the input changed again!
  10. 16selectTodosByText(state, { search: «what» });

It does not matter if at one point in time we called the selector with the same
props, if the last function execution does not match the same inputs as the
current function execution then it will recompute the query.

# When should I build a selector creator?

A selector creator is a function that creates selectors. This allows us to get
around the last result cache limitation of createSelector that was described
previously. A selector creator is particularly useful when we use the same
selector in multiple places on the same page.

# Example

  • 1import React from «react»;
  • 2import { useSelector } from «react-redux»;
  • 3
  • 4const Page = () => {
  • 5 const whenTodos = useSelector((state: State) =>
  • 6 selectTodosByText(state, { search: «when» }),
  • 7 );
  • 8 const whereTodos = useSelector((state: State) =>
  • 9 selectTodosByText(state, { search: «where» }),
  • 10 );
  • 11
  • 12 return (
  • 13
  • 14
  • 15 {whenTodos.map((todo) => (

16 {todo.text}

  1. 17 ))}
  2. 18
  3. 19
  4. 20 {whereTodos.map((todo) => (

21 {todo.text}

  • 22 ))}
  • 23
  • 24
  • 25 );
  • 26};

In this case, createSelector is rendered useless because we are constantly
changing the inputs being supplied to our selector selectTodosByText.

However, if we build a function that creates selectors for us, then we can build
as many createSelector for our one query as many times as we want.

  1. 1const createSelectorTodosByText = () =>
  2. 2 createSelector(
  3. 3 selectTodos,
  4. 4 (state: State, props: { search: string }) => props.search,

5 (todos, search) => todos.filter((todo) => todo.text.includes(search)),

  • 6 );
  • 7
  • 8import React from «react»;
  • 9import { useSelector } from «react-redux»;
  • 10
  • 11// do NOT create these variables inside the react component without
  • 12// `useCallback` or `useMemo` because everytime these are called they
  • 13// create a new selector with a blank cache.
  • 14// It's safer to come up with a way to define these outside the
  • 15// react component.
  • 16const selectWhenTodos = createSelectorTodosByText();
  • 17const selectWhereTodos = createSelectorTodosByText();
  • 18
  • 19const Page = () => {
  • 20 const whenTodos = useSelector((state: State) =>
  • 21 selectWhenTodos(state, { search: «when» }),
  • 22 );
  • 23 const whereTodos = useSelector((state: State) =>
  • 24 selectWhereTodos(state, { search: «where» }),
  • 25 );
  • 26
  • 27 // rendering both todos on the page
  • 28};

This is great because now we have two separate memoized selectors that we can
use in this react component without popping their cache.

Calling createSelector within a react component creates a new memoized
selector on every single run of the component. This defeats the purpose of using
reselect.

# Example

  1. 1const makeSelectTodosById = (id: string) => {
  2. 2 return createSelector(selectTodos, (todos) => todos[id]);
  3. 3};
  4. 4
  5. 5const ToDoPage = (props: { id: string }) => {
  6. 6 // this **creates** a new memoized selector everytime the react component
  7. 7 // re-renders, which wipes the cache for the selector!
  8. 8 const todo = useSelector(makeSelectTodosById(props.id));
  9. 9};

Selector builders are not a good way to pass props into the selector.

# Passing props to a selector

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *