Почему компонент повторно монтируется после изменения состояния? И как исправить?
При использовании связки Context + useReducer + children, дочерний компонент GoodsFilter почему-то заново монтируется после диспатча состояния, можно убедится в этом через консоль изменяя значение фильтра товаров. Вопрос: почему компонент GoodsFilter заново монтируется каждый раз при изменении стейта, и можно ли убрать этот лишний монтаж? так как изза этого я не могу сохранить локальное состояние компонента GoodsFilter, потому что оно постоянно инициализируется первоначальным значением
GoodsContextProvider
import { useReducer, createContext, useCallback } from "react";
export const GoodsContext = createContext()
const fakeApiCall = (inStockFilter) => {
const data = [
{ id: 1, name: 'Apples', inStock: false, quantity: 0 },
{ id: 2, name: 'Oranges', inStock: true, quantity: 25 },
{ id: 3, name: 'Pears', inStock: true, quantity: 90 },
{ id: 4, name: 'Pineapples', inStock: false, quantity: 0 },
{ id: 5, name: 'Bananas', inStock: true, quantity: 43 },
{ id: 6, name: 'Mangoes', inStock: true, quantity: 15 },
]
return new Promise((resolve) => {
setTimeout(() => {
if (inStockFilter === null) {
return resolve(data)
}
resolve(data.filter(item => item.inStock === inStockFilter))
}, 500);
})
}
const reducer = (state, action) => {
switch (action.type) {
case 'pending':
return { ...state, loading: true }
case 'success':
return { ...state, loading: false, items: action.payload }
case 'error':
return { ...state, loading: false, error: action.payload }
case 'setFilter':
return { ...state, inStockFilter: action.payload }
default:
break;
}
}
const initState = {
items: [],
loading: false,
error: '',
inStockFilter: null,
}
const GoodsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initState)
const fetchGoods = useCallback(async () => {
try {
dispatch({ type: 'pending' })
const data = await fakeApiCall(state.inStockFilter)
dispatch({ type: 'success', payload: data })
} catch (error) {
dispatch({ type: 'error', payload: error })
}
}, [state.inStockFilter])
return (
<GoodsContext.Provider value={{ state, dispatch, fetchGoods }}>
{children}
</GoodsContext.Provider>
)
}
export default GoodsContextProvider
App
import GoodsScreen from "./components/GoodsScreen";
import GoodsContextProvider from "./context/GoodsContextProvider";
function App() {
return (
<GoodsContextProvider>
<GoodsScreen />
</GoodsContextProvider>
);
}
export default App;
GoodsScreen
import GoodsList from "./GoodsList"
const GoodsScreen = () => {
return (
<div>
<GoodsList />
</div>
)
}
export default GoodsScreen
GoodsList
import { useContext, useEffect } from "react"
import { GoodsContext } from "../context/GoodsContextProvider"
import GoodsFilter from "./GoodsFilter"
const GoodsList = () => {
const { state, fetchGoods } = useContext(GoodsContext)
useEffect(() => {
fetchGoods()
}, [fetchGoods])
if (state.loading) return <h3>loading...</h3>
return (
<>
<GoodsFilter />
<hr />
<ul>
{state.items.map((good => <li key={good.id}>{good.name} - {good.quantity}</li>))}
</ul>
</>
)
}
export default GoodsList
GoodsFilter
import { useState, useEffect, useContext } from "react"
import { GoodsContext } from "../context/GoodsContextProvider"
const GoodsFilter = () => {
const [localState, setLocalState] = useState('init')
const { state, dispatch } = useContext(GoodsContext)
useEffect(() => {
// you can notice, console.log executing on every time i changed a state
console.log('Component mounted');
}, [])
const filterChangeHandler = (e) => {
let filterValue = null
if (e.target.value === 'true') filterValue = true
if (e.target.value === 'false') filterValue = false
// state changing is here
dispatch({ type: 'setFilter', payload: filterValue })
// also i can't save local state because of component mounting on every render
setLocalState('my new test state')
}
return (
<div>
<select
name="inStockFilter"
onChange={filterChangeHandler}
defaultValue={state.inStockFilter}
>
<option value='all'>show all</option>
<option value='true'>in stock</option>
<option value='false'>not available</option>
</select>
<p>local state is : {localState}</p>
</div>
)
}
export default GoodsFilter
Источник: Stack Overflow на русском