State updation 101

We only need to re-render when a state have changes, for more clarity, when values changes.

/* wrong way */
const [data, setData] = useState([]);

const addData = (newValue) => {
    data.push(newValue);
    setData(data);
}

But this won’t trigger a re-render, why? we have added a element to array, so it should change right? But it’s expensive to make such comparison. To improve performance react is using shallow comparison. It only check if the reference had changed. If this seems confusing checkout blog to understand more.

/* proper way */
const [data, setData] = useState([]);

const addData = (newValue) => {
    cosnt newData = [...data, newValue];
    setData(newData);
}

Now state updation will trigger re-render, because newData have a different reference.

What about nested data

const [address, setAddress] = useState({
    office: {
	coordinates: {
	    lat: 0,
	    long: 0
	}
    }
})

Now we have a nested state, what is the right way to update this?

Changing the reference alone

const updateCoord = (lat, long) => {
    const newAddress = {...address};
    newAddress.office.coordinates.lat = lat;
    newAddress.office.coordinates.long = long;
    setAddress(newAddress);
}

Yep, this will trigger a re-render as expected. But there is an issue.

<Map coord={address.office.coordinates} />

const Map = ({coord}) => {
    useEffect(() => {
	// fetch data when coord changes
	getNewData(coord);
    }, [coord]);

    reutrn (
	<div> ... </div>
    );
}

When coord change we should trigger API call, but useEffect is using shallow comparison, as we mutated the coordinates object API call will not be triggered.

Change reference in all levels

we need to create a copied and updated object for each level of nesting that is affected.

const updateCoord = (lat, long) => {
    const newAddress = {
    ...address
    office: {
	...address.office,
	coordinates: {
		lat,
		long
	    }
	}
    };
    setAddress(newAddress);
}

As reference changed API call will be triggered.

Reference