2
votes

Whenever I set a variable using useState, the value is not immediately reflected. Here I have a useEffect that calls computePriceSummary();

useEffect(() => {
    computePriceSummary();
}, []);

computePriceSummary calls three functions as shown:

const computePriceSummary = () => {
   computeSubtotal();
   computeTaxes();
   computeTotal();
};

These functions set variables using useState:

const computeTaxes = () => {
   let taxes = 0.05 * subtotal;
   setTaxes(taxes);
};

const computeTotal = () => {
   setTotal(taxes + subtotal);
 };

const computeSubtotal = () => {
   let subtotal = cart.products.reduce((acc, item) => {
     return (acc += item.product.price * item.quantity);
   }, 0);
   setSubtotal(subtotal);
 };

The values shown in the browser are:

Subtotal: $1200
Taxes: $0
Total: $0

The solution in Stackoverflow suggests using a useEffect to track the variables so I did:

useEffect(() => {
   console.log("Use effect to immediately set variables");
 }, [subtotal, taxes, total]);

The result is still the same.

2
could you reproduce this on codesandbox? - hgb123

2 Answers

4
votes

When you call setSubtotal, the subtotal variable does not get updated instantly. That means that your calls to computeTaxes and computeTotal are operating on the old value.

This would be fine if your effect is called on every update to the state or the props, but this definition:

useEffect(() => {
    computePriceSummary();
}, []);

means that this effect is only called once. In order to fix it, use something like this:

useEffect(() => {
    computePriceSummary();
}, [subtotal, taxes, total]);

The component will be re-rendered on each change of your state variables, and by having them in the dependencies of the effect, that effect will be retriggered properly.

Adding a second effect (the one where you use console.log) will not cause the first effect to be re-run in your case.

1
votes

Not only your function computePriceSummary() is not called anywhere in the useEffect hook, but you won't see any re-render even if you did include those 3 states in the dependency array.

useEffect(() => {
    computePriceSummary();
  }, [taxes, total, subtotal]);

You'd get the following warning:

React Hook useEffect has a missing dependency: 'computePriceSummary'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)

You can see it in action in this demo. Data is being changed, but re-renders won't trigger in this component.

If you included computePriceSummary in the dependency array (4 now in total), the re-renders will trigger.

But there is a new warning:

The 'computePriceSummary' function makes the dependencies of useEffect Hook (at line 37) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'computePriceSummary' definition into its own useCallback() Hook. (react-hooks/exhaustive-deps).

Let's fix that wrapping the function with useCallback hook. useCallback makes the function itself only change when necessary, having a dependency array of its own.

const computePriceSummary = useCallback(() => {
    computeSubtotal();
    computeTaxes();
    computeTotal();
  }, []);

Again, you get a dependency warning, now it asks to include all the other functions as dependencies:

React Hook useCallback has missing dependencies: 'computeSubtotal', 'computeTaxes', and 'computeTotal'. Either include them or remove the dependency array. (react-hooks/exhaustive-deps)

And we fix this using again useCallback hook with all other functions. Passing the relevant state (the one that is being used inside each function) to their own dependancy array.

This is getting repetitive, isn't it? You might want to do these tasks in the backend and consume the reduced data in React.

Here you can see a full demo with an emulated cart that updates when a button is pressed. State is correctly setup, there are no dependencies warnings and the functionaly works just fine.