william007
william007

Reputation: 18545

how to let component rerender during props change

I want to achieve the effect when PRODUCTS change (during button click), the list changes as well with first category change to "haha".

The whole objective is to let component rerender during props change. But seems like it doesn't work. Below is the code.

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

const FilterableProductTable = (props) =>{
    return (

        <ul>
            {props.products.map((product) =>
                (<li>{product.category}</li>)
            )}
        </ul>
    );
}

const PRODUCTS = [
    { category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football' },
    { category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball' },
    { category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball' },
    { category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch' },
    { category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5' },
    { category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7' }
];

const Home = () => {
    const [productData, changeProduct] = useState(PRODUCTS);
    const handleClick = () => {
        PRODUCTS[0].category = 'haha';
        changeProduct(PRODUCTS);
        console.log(PRODUCTS);
    };
    return (
        <>
            <button onClick={handleClick}>haha</button>
            <FilterableProductTable products={productData} />
        </>
    );
};

export default Home;

You can try it here:

Edit nextjs redux-react-hooks (forked)

How to let FilterableProductTable rerender during props change?

Upvotes: 0

Views: 51

Answers (3)

Shan
Shan

Reputation: 1568

useState will trigger a re-render, only if its current value is different than the current state. It will do a Object.is or === comparision. Here you are setting the same reference PRODUCTS as in the defaultState and value passed to changeProduct is also having the same reference as PRODUCTS which will not trigger a re-render.

Quoting from React DOCS

If your update function returns the exact same value as the current state, the subsequent rerender will be skipped completely.

const handleClick = () => {
  const res = productData.map((prod,index) => index === 0 ? {...prod,category:"haha"} : prod)
  changeProduct(res);
  console.log(res);
};

map will create a new array every time.

Upvotes: 1

ahmetkilinc
ahmetkilinc

Reputation: 684

when changing a property in array, react doesnt see it as a change, you can use spread operator to clone your array and set new array:

changeProduct([...PRODUCTS])

forked codesandbox

Upvotes: 0

Nicholas Tower
Nicholas Tower

Reputation: 84982

When you set state in a function component, react does a === between the old and new state. If they're the same, then react skips rendering. Since you are mutating state, it's the same array before and after, and the render is skipped.

The fix is to keep your state immutable. Don't modify the old array, but instead create a new one:

const handleClick = () => {
  changeProducts(prev => {
    const next = [...prev];
    next[0] = {
      ...next[0],
      category: 'haha';
    }
    return next;
  });
}

Upvotes: 1

Related Questions