# Домашнее задание

### ⚡ 1) Зачистить стейт при вылогинивании

\
Если вылогиниться из приложения, то таски и тудулисты, которые находятся в стейте не зачищаются. И это не есть хорошо.\
В данном [видео ](https://youtu.be/w45bp-zCqCc)описана проблема и ее реализация на redux. Ваша задача сделать это на redux toolkit&#x20;

### &#x20;⚡2) Работа с селекторами&#x20;

Рефакторим все селекторы в приложении согласно **auth.selectors.ts**

<figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2F7ksqGcnGYkwuea6vHKkA%2Fimage.png?alt=media&#x26;token=cc711283-4f82-433a-9573-e7a225ddbf66" alt=""><figcaption></figcaption></figure>

В [данном видео](https://youtu.be/6H41zRDbq9Y) (запись с урока) максимально подробно рассказываю про работу с селекторами

\-----------------------------------------------------------------------------------------------------

### 🔗[Многие моменты я взял из данной статьи](https://habr.com/ru/articles/564004/)

#### ⚡&#x31;**)  Intro**

Давайте зайдем в компонент **`App.tsx`** и посмотрим как мы достаем данные из стейта

```typescript
const isLoading = useAppSelector((state) => state.app.isLoading);
```

Казалось бы и что тут рассказывать, но на самом деле есть нюансы которые нужно знать

#### ⚡&#x32;**)**  useAppSelector

⚡2.1) Вводная часть

Давайте перепишем доставание **`isLoading`** вот так

```typescript
const { isLoading } = useAppSelector((state) => state.app);
```

Я довольно часто вижу такую запись. Почему так студенты любят писать. Допустим нам из app стейта нужно достать 3 значения (**`isLoading`**, **`isAppInitialized`**, **`unHandleActions`**)

И теперь сравним 2 варианта

{% tabs %}
{% tab title="1 вариант" %}
{% code overflow="wrap" lineNumbers="true" %}

```typescript
const { isLoading, isAppInitialized, unHandleActions } = useAppSelector((state) => state.app);
```

{% endcode %}
{% endtab %}

{% tab title="2 вариант" %}
{% code overflow="wrap" lineNumbers="true" %}

```typescript
const isLoading = useAppSelector((state) => state.app.isLoading);
const isAppInitialized = useAppSelector((state) => state.app.isAppInitialized);
const unHandleActions = useAppSelector((state) => state.app.unHandleActions);
```

{% endcode %}
{% endtab %}
{% endtabs %}

Сравнивая 1 и 2 вариант, очевидно что в 1 случае кода писать меньше, именно поэтому так и любят писать.

Теперь проверьте, поменялось ли что-нибудь или нет ?&#x20;

Ответ: Нет, ничего не поменялось. Значит получается без разницы как писать? А вот здесь ответ нет, разница существенная.

{% hint style="info" %}
На работе приложения (его логической части) это никак не скажется. А на чем скажется - на перфомансе (как часто наши компоненты перерисовываться).
{% endhint %}

⚡ 2.2) Начнем со **2 варианта**\
Вот такой у нас будет стартовый код

{% code title="App.tsx " overflow="wrap" lineNumbers="true" %}

```tsx
export const App = () => {
  console.log("App render");

  const dispatch = useAppDispatch();

  const isLoading = useAppSelector((state) => state.app.isLoading);
  const isAppInitialized = useAppSelector((state) => state.app.isAppInitialized);
  const unHandleActions = useAppSelector((state) => state.app.unHandleActions);

  // const { isLoading, isAppInitialized, unHandleActions } = useAppSelector((state) => state.app);

  console.log("isLoading: ", isLoading);
  console.log("isAppInitialized: ", isAppInitialized);
  console.log("unHandleActions: ", unHandleActions);

  useEffect(() => {
    setTimeout(() => {
      dispatch(appActions.setIsLoading({ isLoading: true }));
    }, 3000);
  }, []);

  return <div className="App">{isLoading && <LinearProgress />}</div>;
};
```

{% endcode %}

Вот, что мы увидим в консоли

<div align="left"><figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2FHz0I1DujdrtDlWl4xN16%2Fimage.png?alt=media&#x26;token=a73c3075-9849-498c-adb1-346df9023fab" alt=""><figcaption></figcaption></figure></div>

**`isLoading`** изменил свое состояние, соответственно компонент App перерисовался. Все логично, все хорошо.

* теперь давайте в useEffect через 3 секунды будем менять **error** (вмеcто **`isLoading`**)

```typescript
dispatch(appActions.setAppError({ error: "Error" }));
```

<div align="left"><figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2F9gM5r8r594Q6Ib3fGBlp%2Fimage.png?alt=media&#x26;token=d131997a-3e31-4175-9893-d09a580cae1d" alt=""><figcaption></figcaption></figure></div>

&#x20;⚡ 2.3) Теперь все тоже самое проделаем для **1 варианта**

1\) Для первого варианта, когда меняем крутилку ничего не поменялось

2\) Но когда мы меняем ошибку, то получим следующий итог\\

<div align="left"><figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2FPbLhx3wBOuzycgtV79M3%2Fimage.png?alt=media&#x26;token=c66e5bed-dfeb-4a43-a207-6e1bb18432de" alt=""><figcaption></figcaption></figure></div>

В **`App.tsx`** мы не достаем  error, но она все равно перерисовывается 🤯🤯🤯

{% hint style="info" %}
Так происходит именно по причине неправильного доставания данных из стейта. Мы подписываемся в useSelector на весь **`app`**\
\
\&#xNAN;**`useAppSelector(state => state.app)`**\
\
Соответственно если в app стейте поменяется любое свойство, то все компоненты в которых данные достаются подобным образом  будут перерисовываться вне зависимости используется там это свойство или нет &#x20;
{% endhint %}

{% hint style="warning" %}

* Берем максимально точечно данные из стейта
* Никогда не используем деструктуризация в селекторах (Можно этим правилом пренебречь в том случае, если уверены, что все свойства стейта используются в компоненте)
  {% endhint %}

#### ⚡&#x33;**)**  Selector&#x20;

Опять возвращаемcя к нашему исходному селектору

```typescript
const isLoading = useAppSelector((state) => state.app.isLoading);
```

⚡3.1) Как правило в разных компонентах нам нужно доставать одинаковые данные из стейта. Т.е. Код я указал выше может использовался в 10 компонентах. \
А теперь представьте что у нас в приложении мы решели заменить свойство **`isLoading`** на **`loadingStatus.`** К чему это приведет?

Это приведет к тому, что нам нужно идти в 10 компонент и менять эти свойства, что не очень приятно.&#x20;

⚡3.2) При каждом рендере - новая функция. Cелектор будет вызываться при каждом рендере, а не только когда обновились данные в сторе.

⚡3.3) Логика получения данных из структуры стора находится внутри компонента.\
Но зачем компоненту знать об этом?

По этим причинам селекторы выносят в отдельные функции, которые в принципе так и называют.

Создадим файл **`app.selector.ts`** и вынесем туда кода доставания значения из стейта

<div align="left"><figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2F0BpKe88gTalrqBVdue8q%2Fimage.png?alt=media&#x26;token=3a593c26-1cd7-4dd6-a156-88cca92373b2" alt=""><figcaption></figcaption></figure></div>

Функции селекторы называют по разному. Встречал следующие варианты:

* **select**IsLoading
* isLoading**Selector**
* иногда вообще в названии не указывают select (selector) и группируют все селекторы в один объект (namespace). Я так не делал никогда : )

{% hint style="warning" %}

* **Все селекторы выносим в файл с селекторами**
* **Селекторы кладем в отдельные файлы соответствующие фиче.**&#x20;
  {% endhint %}

###

#### ⚡&#x34;**)**  [**Reselect**](https://www.npmjs.com/package/reselect)

Говоря про селекторы нельзя не упомянуть про [**createSelector**](https://redux-toolkit.js.org/api/createSelector)

⚡4.1) Теория

Селекторы можно разделить на 2 типа

* **простые (обычные) селекторы** - это селекторы которые просто достают данные из стейта

```typescript
const selectCount = (state: RootState) => state.counter.value;
const isLoadingSelector = (state: RootState) => state.app.isLoading;
const packsSelector = (state: RootState) => state.packs.cardPacks;
```

Такие селекторы стоит использовать всегда, когда мы напрямую ссылаемся на данные из стора. Даже если возвращаемое значение является объектом, не стоит беспокоиться - мы лишь возвращаем ссылку на уже существующий объект, который находится в стейте, так что ни к каким проблемам это не приведет.

* **сложные селекторы**, это селекторы в которых  мы делаем сортировку / фильтрацию или сложные вычисления

```typescript
const selectSubtotal = createSelector(selectShopItems, items =>
  items.reduce((subtotal, item) => subtotal + item.value, 0)
)

const selectTax = createSelector(
  selectSubtotal,
  selectTaxPercent,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

const selectTotal = createSelector(
  selectSubtotal,
  selectTax,
  (subtotal, tax) => ({ total: subtotal + tax })
)
```

⚡4.2) Практика

{% hint style="danger" %}
Задача\
&#x20;Отображать только те колоды у которых в имени содержится буква **f**.

* в проекте с карточками поиск, фильтрации, сортировка реализована на сервере, но ради примера давайте будем с сервера запрашивать 100 колод и фильтровать их по имени локально.
  {% endhint %}

1\) Захаркодим запрос за 100 колодами

```typescript
getPacks: () => {
  return instance.get<FetchPacksResponseType>("cards/pack?pageCount=100");
}
```

2\) Вынесем получение колод в селектор

```typescript
import { RootState } from "app/store";

export const packsSelector = (state: RootState) => state.packs.cardPacks;
```

3\) Теперь сделаем сложный селектор в котором собственно и будем осуществлять логику по фильтрации

{% code title="packs.selector.ts" overflow="wrap" lineNumbers="true" %}

```typescript
export const filteredByNamePacksSelector = (state: RootState) => {
  console.log("filteredByNamePacksSelector");
  const packs = state.packs.cardPacks;
  return packs.filter((p) => p.name.includes("f"));
};

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
```

{% endcode %}

Теперь воспользуйтесь данным селектором в Packs.tsx. Попробуйте фильтровать по разным буквам и убедитесь, что все отрабатывает верно 👍

4\) Для того, чтобы понять что собственно не так, давайте компонент **`Counter`** (мог бы быть абсолютно любой компонент в котором используется useSelector) уберем из роутинга и вставим на одном уровне с App

```typescript
root.render(
  <Provider store={store}>
    <App />
    <Counter />
    <RouterProvider router={router} />
    <GlobalError />
  </Provider>
);
```

5\) Напишем **`console.log('render ${component}')`** в **Packs.tsx** и **Counter.tsx**

И теперь измените меняйте значение Counter

<div align="left"><figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2FryTkFy2l6FOznus6zxlG%2Fimage.png?alt=media&#x26;token=7d1d7103-7f2a-41af-a125-970856c5ac72" alt=""><figcaption></figcaption></figure></div>

Мы меняем значение счетчика, который вообще никоим боком не связан с колодами, но мы видим как она перерисовывается 🤯🤯🤯. \\

{% hint style="info" %}
Такое поведение мы получаем из-за того, что наш сложный селектор возвращает новый массив.&#x20;

**useSelector** не просто достает данные из стейта, он еще делает подписку. И рендер в компонентах происходит когда приходит новое значение.

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

Сравнение под капотом происходит по ссылке, а как мы знаем объекты / массивы сравниваются по ссылке и два одинаковых массива никогда не будут равны.&#x20;

Максимально подробно эту тему освятил АйтиСиняк

* [**Все ли вы знаете о useSelector**](https://youtu.be/SVG-x-4BQic)
* [**Самый скользкий механизм в Redux**](https://youtu.be/tbfo28Q5eag)
  {% endhint %}

6\) И вот чтобы избежать вот такого поведения нам и нужен **`createSelector`**

{% hint style="info" %}
**`createSelector`** - это функция из библиотеки `reselect`, которая позволяет создавать селекторы в Redux-приложениях. Селекторы используются для извлечения данных из хранилища Redux, а `createSelector` помогает оптимизировать производительность приложения, кэшируя результаты вызова селекторов и предотвращая повторные вычисления.&#x20;
{% endhint %}

{% code title="packs.selector.ts" overflow="wrap" lineNumbers="true" %}

```typescript
export const filteredByNamePacksSelector = createSelector(
  // 1 - массив селекторов
  [packsSelector],
  // 2 - функция, которая принимает данные от селекторов и возвращает новое значение
  (packs) => {
    console.log("filteredByNamePacksSelector");
    return packs.filter((p) => p.name.includes("f"));
  }
);
```

{% endcode %}

По итогу получим вот такой результат 🚀

<div align="left"><figure><img src="https://1927446408-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeTey9vZSVNUWifiqBVBF%2Fuploads%2Fp2At6JIPOHiSF35cIAHC%2Fimage.png?alt=media&#x26;token=fc4b9239-5ad1-4e1f-a98d-ec93563d057f" alt=""><figcaption></figcaption></figure></div>

🔗 [**Примеры работы с useSelector из документации**](https://react-redux.js.org/api/hooks#useselector-examples)

☝ Вывод по использованию createSelect такой

1\) **Используйте мемоизированные селекторы когда:**&#x20;

* В селекторе есть тяжелые вычисления (фильтрация, сортировка, сложное преобразование данных, и так далее)
* Результатом вызова селектора является объект. Ну и конечно же, это касается массивов и различных структур вроде Set и Map, так как они тоже являются объектами.

2\) [**Есть мнение о том чтобы без анализа селектора всегда использовать reselect**](https://www.youtube.com/watch?v=qWzxwzcjttk)
