Мемоизированный селектор повторного выбора с RTKQuery selectFromResult по-прежнему повторно отображает компонент

Я работаю с конечной точкой API, которая возвращает случайные данные для определенного свойства (unstableProperty) каждый раз, когда оно вызывается для одного и того же объекта. Это поведение показано ниже:

// First API Call
GET /entities/3
{
  "name": "Entity Name",
  "message": "This is some entity description",
  "files": [
    { "fileName": "File1", "unstableProperty": "bbserg" },
    { "fileName": "File2", "unstableProperty": "thslkuygseaf" }
  ]
}

// Second API Call (5 seconds later)
GET /entities/3
{
  "name": "Entity Name",
  "message": "This is some entity description",
  "files": [
    { "fileName": "File1", "unstableProperty": "ystgrgazazrg" },
    { "fileName": "File2", "unstableProperty": "strhsryjarehaerh" }
  ]
}

Контекст

Бизнес-правило требует опроса этой конечной точки каждые X секунд на наличие изменений. Однако я не хочу, чтобы мой компонент обновлялся каждый раз, поскольку он включает в себя таблицу, потенциально содержащую сотни строк, и каждая строка содержит миниатюру. Каждый раз повторная обработка таблицы приводит к проблемам с производительностью и заставляет миниатюры мерцать.

Проблема

Я пытаюсь использовать этот пример из документации RTKQuery, чтобы предотвратить повторный рендеринг, но похоже, что это не работает должным образом. Несмотря на настройку селектора, который отфильтровывает нестабильные свойства, компонент по-прежнему выполняет повторную обработку каждого интервала опроса.

Я подготовил подробный пример на этом коде и ящике: https://codesandbox.io/p/sandbox/modest-snyder-vyxkxd?file=%252Fsrc%252Fapi%252FbaseApi.ts

Вот как я смоделировал нестабильное свойство для демонстрации:

let calls = 0;

export const baseApi = createApi({
  baseQuery: fetchBaseQuery(),
  endpoints: (build) => ({
    getTestData: build.query<TestDataResponse, void>({
      queryFn: () => ({
        data: {
          name: "Entity Name",
          message: "This is some entity description",
          files: [
            { fileName: "File1", unstableProperty: `value_${calls++}` },
            { fileName: "File2", unstableProperty: `value_${calls++}` },
          ],
        },
      }),
    }),
  }),
});

Мой компонент, который показывает проблему, использует этот запрос следующим образом:

  const selectOnlyStableData = useMemo(() => {
    return createSelector(
      (data: TestDataResponse | undefined) => data,
      (data: TestDataResponse | undefined) => {
        if (!data) return undefined;

        // Select everything from data except files' unstableProperty
        const stableData = {
          name: data.name,
          message: data.message,
          files: data.files.map((f) => ({
            fileName: f.fileName,
          })),
        };

        return stableData;
      }
    );
  }, []);

  const { data } = useGetTestDataQuery(undefined, {
    pollingInterval: 5000,
    selectFromResult: ({ data }) => ({
      data: selectOnlyStableData(data),
    }),
  });

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

Вопрос

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

Обратите внимание, что я не могу просто удалить это нестабильное свойство (например, в transformResponse), потому что мне нужно значение этого нестабильного свойства в других компонентах.

Есть ли у вас какой-либо способ определить, когда полученные данные на самом деле отличаются? Что-нибудь вообще, что можно было бы использовать в качестве зависимости для запоминания? Я довольно долго возился с вашей песочницей и не смог заставить ничего работать так, как вы пытаетесь. Технически вы получаете новые результаты каждые 5 секунд, поэтому мемоизация в вашей реализации довольно неудачна. Я бы посоветовал подумать о запоминании свойства data, которое вы передаете... но опять же, вам понадобится что-то вроде стабильной ссылки на данные для запоминания.

Drew Reese 02.05.2024 03:04

Возможно, опрос — не лучшее решение для вас, если вы не можете предоставить в данных какое-то стабильное поле «updateHash» или «lastUpdatedAt». Как часто данные на самом деле меняются? Рассматривали ли вы сокеты или публикацию/подписку и «рассылку» обновлений клиентам или проверку связи с ними для получения?

Drew Reese 02.05.2024 03:06

@DrewReese Я не являюсь владельцем API, поэтому имею ограниченное влияние на его дизайн. Я поговорил с владельцем API и, насколько я понимаю, нестабильное свойство — это хэш свойств файла. Даже если значения, используемые для хеширования, остаются прежними, алгоритм хеширования все равно каждый раз создает другой хэш. Поэтому я уверен, что значение нестабильного свойства будет меняться каждый раз, когда я вызываю конечную точку API.

Rychu 02.05.2024 08:50

@DrewReese API требует, чтобы я передал эти нестабильные свойства для операций переименования и удаления. Вот почему мне нужно иметь их в хранилище и найти способ повторно отображать мой компонент только при изменении любых других свойств, за исключением нестабильных. Спасибо за ваше время, я очень ценю ваш вклад!

Rychu 02.05.2024 08:50

Спасибо за подробности, этот «хеш» больше похож на идентификатор транзакции/синхронизации/токен, что не очень полезно для рендеринга React. Думаете, вы могли бы отредактировать , включив в него пример последующего кода, в котором слишком часто возникают проблемы? Если бы мы могли увидеть более полный и репрезентативный минимально воспроизводимый пример проблемного кода, мы могли бы быть лучше готовы прийти к другому или более оптимальному решению.

Drew Reese 02.05.2024 09:03

@DrewReese, конечно, я могу отредактировать и предоставить более подробную информацию. Сейчас я потрачу на это несколько часов, и если не найду решения, я обновлю свой код. Просто обновление: я попробовал обернуть мемоизированный селектор другим селектором, чтобы добавить еще один уровень мемоизации, но это не помогло. Я не уверен, что мне здесь не хватает — второй селектор теперь получает на вход стабильные данные: codeandbox.io/p/sandbox/…

Rychu 02.05.2024 09:13

@DrewReese У меня получилось! Думаю, опубликую это как ответ: codeandbox.io/p/sandbox/…

Rychu 02.05.2024 09:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы спросите меня, в документации есть немного спрятанная опция resultEqualityCheck. Это упоминается здесь в примере в самом низу страницы и кратко документировано здесь. Это свойство делает именно то, что мне нужно — сравнивает текущий и предыдущий выходные данные и возвращает новую ссылку только в том случае, если они не идентичны. Из документации:

Если предоставлено, используется для сравнения вновь созданного выходного значения с предыдущими значениями в кэше. Если совпадение найдено, возвращается старое значение. Это касается распространенного случая использования todos.map(todo => todo.id), когда обновление другого поля в исходных данных вызывает перерасчет из-за измененных ссылок, но выходные данные остаются фактически теми же.

Фиксированный селектор выглядит следующим образом:

const selectOnlyStableData = useMemo(() => {
    return createSelector(
      (data: TestDataResponse | undefined) => data.data,
      (data: TestDataResponse | undefined) => {
        if (!data) return undefined;

        // Select everything from data except files' unstableProperty
        const stableData = {
          name: data.name,
          message: data.message,
          files: data.files.map((f) => ({
            fileName: f.fileName,
          })),
        };

        return stableData;
      },
      {
        memoizeOptions: {
          resultEqualityCheck: deepEqual,
        },
      }
    );
  }, []);

Исправлены коды и ящик: https://codesandbox.io/p/sandbox/agitated-hugle-xtyt78?file=%252Fsrc%252FcomComponents%252FProblem.tsx

Хорошие находки, спасибо, что поделились.

Drew Reese 03.05.2024 01:11

Другие вопросы по теме

Почему мое второе значение OnClick Isloading не работает в запросе на мутацию RTK Query?
Как использовать одну и ту же функцию запроса RTK несколько раз с разными параметрами в одном и том же компоненте?
Как получить доступ к «селекторам» из фрагмента в наборе инструментов Redux
Обновите токен доступа с помощью набора инструментов redux-toolkit asyncThunks
Недействительность конечной точки Redux Toolkit Query (RTKQ) и повторная проверка срабатывают несколько раз
Как избежать вспышки ненужного компонента в течение 1 секунды после получения данных в React?
Поведение события React OnClick неожиданное
Как передать токен из локального хранилища (асинхронное хранилище) на серверную часть во время вызова API
Компонент «Таблица реакции-табулятора» выдает ошибку при передаче данных по состоянию редукции
Реагировать на сокращение - useLoginMutation не является функцией или ее возвращаемое значение не является итеративным