следующий js с редуксом и повторным выбором - PullRequest
1 голос
/ 11 января 2020

У меня проблема с тем, что когда я использую перевыбор с помощью next. js и redux, компонент не будет визуализироваться, когда состояние редукции было изменено редуктором.

В контейнере у меня есть запомненная функция селектора, но селектор вызывается до того, как контейнер и контейнер не визуализируются после действия CATEGORIES_LOADING_SUCCEEDED.

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

Полный код здесь

Вот некоторая информация, которая может иметь отношение к делу:

страницы / индекс. js

import React from "react";
import Menu from "../components/Menu";

function HomePage() {
  return <Menu />;
}

export default HomePage;

Компоненты / Меню. js

<code>import React, { memo, useMemo } from "react";
import { useSelector } from "react-redux";
import { selectCategoriesNested } from "../store/selectors";
import { useCategories } from "../hooks";

function Menu(props) {
  const { categories } = props;

  return (
    <pre>{JSON.stringify(categories, undefined, 2)}
); } const MemoMenu = памятка (Меню); const MenuContainer = props => {console.log ("Запуск MenuContainer"); useCategories (); const category = useSelector (selectCategoriesNested); const newProps = useMemo (() => ({... реквизиты, категории}), [реквизиты, категории]); console.log («Конец MenuContainer», категории); возвращение ; }; экспорт по умолчанию MenuContainer;

store / initStore. js (используется _app. js)

import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunkMiddleware from "redux-thunk";
import {
  CATEGORIES_LOADING,
  CATEGORIES_LOADING_SUCCEEDED
} from "./actions";

const initState = {
  categories: {
    requested: false,
    data: {}
  }
};

const rootReducer = (state = initState, action) => {
  const { type, payload } = action;
  console.log("reducer:", type);
  if (type === CATEGORIES_LOADING) {
    return {
      ...state,
      categories: { ...state.categories, requested: true }
    };
  }
  if (type === CATEGORIES_LOADING_SUCCEEDED) {
    return {
      ...state,
      categories: {
        ...state.categories,
        data: payload.reduce((categories, category) => {
          categories[category.id] = category;
          return categories;
        }, state.categories.data)
      }
    };
  }
  return state;
};

export const initStore = (initialState = initState) => {
  return createStore(
    rootReducer,
    initialState,
    composeWithDevTools(applyMiddleware(thunkMiddleware))
  );
};

store / selectors. js

import { createSelector } from "reselect";

export const selectCategories = state => state.categories;
export const selectCategoriesRequested = createSelector(
  selectCategories,
  categories => categories.requested
);
export const selectCategoriesData = createSelector(
  selectCategories,
  categories =>
    console.log(
      "selector returning:",
      Object.keys(categories.data).length
    ) || categories.data
);

const nestCategories = data => {
  const categories = Object.values(data).map(category => ({
    ...category,
    subCategories: []
  }));
  const rootCategories = categories.filter(
    category => !category.parent
  );
  const categoriesMap = categories.reduce(
    (categories, category) =>
      categories.set(category.id, category),
    new Map()
  );
  categories.forEach(category => {
    if (
      category.parent &&
      category.parent.typeId === "category"
    ) {
      const parent = categoriesMap.get(category.parent.id);
      parent && parent.subCategories.push(category);
    }
  });
  return rootCategories;
};

// the following does not re render menu when categories
//  are loaded
export const selectCategoriesNested = createSelector(
  selectCategoriesData,
  nestCategories
);
// the following works but breaks menu as pure component
// export const selectCategoriesNested = state =>
//   nestCategories(selectCategoriesData(state));

hooks. js

import { selectCategoriesRequested } from "./store/selectors";
import { loadCategories } from "./store/actions";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";

export const useCategories = query => {
  const dispatch = useDispatch();
  const requested = useSelector(selectCategoriesRequested);
  useEffect(() => {
    if (!requested && process.browser) {
      dispatch(loadCategories(query));
    }
  }, [dispatch, query, requested]);
};

вывод журнала:

initStore.js:18   reducer: @@INIT
Menu.js:15        MenuContainer start
selectors.js:10   selector returning: 0
Menu.js:25        MenuContainer end []
initStore.js:18   reducer: CATEGORIES_LOADING
selectors.js:10   selector returning: 0
Menu.js:15        MenuContainer start
Menu.js:25        MenuContainer end []
initStore.js:18   reducer: CATEGORIES_LOADING_SUCCEEDED
selectors.js:10   selector returning: 1

Если я переключаю селектор на:

export const selectCategoriesNested = state =>
  nestCategories(selectCategoriesData(state));

, я получаю 2 дополнительные консоли logs:

Menu.js:15    MenuContainer start
Menu.js:25    MenuContainer end [{…}]

Интересно, почему селектор вызывается до запуска MenuContainer, а селектор должен вызываться из MenuContainer.

ОБНОВЛЕНИЕ

Добавлены некоторые логи и похоже, что следующий js каким-то образом мутирует в хранилище. Добавлено logs и после действия CATEGORIES_LOADING_SUCCEEDED селектор регистрирует от Same [] до Same ["1"]. Это не может быть возможно, если данные не были изменены, но я не вижу, как мой редуктор изменяет данные.

Я могу ошибаться, но это похоже на ошибку, поэтому я отправил сообщение об ошибке

1 Ответ

0 голосов
/ 12 января 2020

Это смущает, но именно я допустил ошибку. Редуктор мутирует state.categories.data в функции Reduce:

payload.reduce((categories, category) => {
  categories[category.id] = category;
  return categories;
}, state.categories.data);

Я передаю state.categories.data в качестве второго аргумента payload.reduce, а в функции редуктора я его мутирую. Эта функция редуктора должна быть:

payload.reduce((categories, category) => {
  categories[category.id] = category;
  return categories;
}, {...state.categories.data});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...