Запись дынных в глобальный state или Context после fetch (react hooks)
Пытаюсь решить проблему: Реализовал post-запрос на REST-сервер с помощью fetch. Получаю данные и далее, с помощью функции в Context, сохраняю объекты в context, чтобы использовать его в двух разных компонентах. Однако после записи данных в context из fetch происходят аномальные действия: страница как будто полностью ререндерится. Это происходит после каждого нажатия на товар.
Вот скрины из profiler:
Название Context - ProductProvider
ProductProvider:
function ProductsProvider(props) {
//states
const [states, setStates] = useState({
products: [],
basketProducts: [],
totalPrice: "0.0"
});
//Functions
const setProducts = (products) => {
setStates(prevState => ({...prevState, products: products}));
};
const setTotalPrice = (totalPrice) => {
setStates(prevState => ({...prevState, totalPrice: totalPrice}));
};
const setBasketProducts = (basketProducts) => {
setStates(prevState => ({...prevState, basketProducts: basketProducts}));
}
const setAllParameters = (products, basketProducts, totalPrice) => {
if(!products) products = states.products;
if(!basketProducts) basketProducts = states.basketProducts;
if(!totalPrice) totalPrice = states.totalPrice;
setStates({
products: products,
basketProducts: basketProducts,
totalPrice: totalPrice
})
}
const state = {
states,
setProducts,
setBasketProducts,
setTotalPrice,
setAllParameters,
}
return (
<ProductsContext.Provider value={state}>
{props.children}
</ProductsContext.Provider>
);
}
export default ProductsProvider;
IncrDecrButtons:
function IncrDecrButtons(props) {
const [cookie] = useCookies();
const {setAllParameters} = useContext(ProductsContext);
const increaseQuantity = (product) => {
product.quantity +=1;
const newProductPromise = _changeProductQuantity(product);
if(newProductPromise !== null)
newProductPromise.then(res => {
setAllParameters(
res.productList,
res.productList.filter(product => {
return !!product.quantity;
}),
res.totalPrice
)
}).catch(() => {console.log("error2");});
}
const decreaseQuantity = (product) => {
product.quantity -=1;
const newProductPromise = _changeProductQuantity(product);
if(newProductPromise !== null)
newProductPromise.then(res => {
setAllParameters(
res.productList,
res.productList.filter(product => {
return !!product.quantity;
}),
res.totalPrice
)
}).catch();
}
function _changeProductQuantity(product) {
return fetchDataJSON(
process.env.REACT_APP_SPRING_SERVER_PATH,
'POST',
JSON.stringify({
"productId": product.id,
"quantity": product.quantity
})
)
}
function fetchDataJSON(URL, method, body) {
return fetch(URL,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: body
})
.then(_handleResponseJSON)
.catch(_handleResponseError)
}
function _handleResponseError(err){
return Promise.reject(err.message);
}
function _handleResponseJSON(response){
if (response.ok) {
return response.json();
} else {
return Promise.reject(response.statusText);
}
}
return (
<div className={styles.incDecrButtons}>
<button className={[styles.button, styles.button_oval, styles.incDecrButtons__button].join(" ")}
onClick={() => {decreaseQuantity(props.product)}}>
<span className={[styles.button__text, styles.text, styles.text_noSelect].join(" ")}>-</span>
</button>
<span className={[styles.text, styles.text_paragraph, styles.text_noSelect].join(" ")}>
{props.product.quantity}
</span>
<button className={[styles.button, styles.button_oval, styles.incDecrButtons__button].join(" ")}
onClick={() => increaseQuantity(props.product)}>
<span className={[styles.button__text, styles.text, styles.text_noSelect].join(" ")}>+</span>
</button>
</div>
);
}
export default IncrDecrButtons;
Если нужно уточнение вопроса - пишите об этом в комментарии
Ответы (1 шт):
Eсли стейт ProductProvider-a меняется, то он рендерится, и все потомки (по дереву) по дефолту тоже. Это можно оптимизировать.
Если этот рендер не сказывается негативно на производительности на целевых устройствах и объемах данных, оптимизацию можно отложить.
Оптимизировать можно оборачивая некоторые компоненты в React.memo, чтобы отключить ререндер ветки, если пропы компонента не изменились.
Однако, если контекст поменялся, useContext в компоненте ведет к рендеру.
Уменьшить количество охватываемых компонент можно разделив контекст на несколько меньших контекстов.
Можно использовать в провайдере (между стейтом и контекстом) useMemo и useCallback с зависимостями.
Принимая новый список продуктов, можно вычислять реальный diff, и оставлять в списке старые объекты. В js нет встроенного сравнения по содержимому.