У меня проблема с тем, что когда я использую перевыбор с помощью 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"]
. Это не может быть возможно, если данные не были изменены, но я не вижу, как мой редуктор изменяет данные.
Я могу ошибаться, но это похоже на ошибку, поэтому я отправил сообщение об ошибке