Marina
Marina

Reputation: 187

How to prevent app not to render twice? (reactjs)

I'm having a problem with displaying a totalAmount in my app. Because of a two-times rendering, it displays wrong value. I try to use useMemo hook for that.

This is how it looks like (don't bother with css :D )

enter image description here

So it counts like this:

7.99 * 2 + 4.99 = 20.97

the last added item -44.99 hasn't been counted in totalAmount

and it multiplies first item by 2 because of rendering twice, I guess

And here is my code sandbox: https://codesandbox.io/s/objective-kare-805kc?file=/src/components/App.tsx

Upvotes: 0

Views: 157

Answers (1)

Nicholas Tower
Nicholas Tower

Reputation: 85012

The fact that your components are not resilient to multiple renders is itself a problem. In general, you cannot assume your component will only render a certain number of times, and this will become especially true when the upcoming Concurrent Mode is released. Reducing the number of renders may be useful for performance reasons, but i would not consider that a fix to your issue.

The issue appears to be that your reducer is impure. A reducer should be a pure function: it takes in a state and an action, and it produces a new state, with no side effects. But instead, in the middle of your reducer you're calling setFixedAmount and setTotalAmount, which will set state and cause a rerender.

From looking at your code, i think it is a mistake to have three pieces of state. You have products, totalAmount, and fixedAmount, and are trying to write code to keep them all in sync with eachother. Instead a better approach is to just have one state: products. totalAmount and fixedAmount are then derived values calculated based on products.

function App() {
  const [allData] = useState(data.products);

  const [products, dispatch] = useReducer((state: any, action: any): any => {
    switch (action.type) {
      case "ADD_PRODUCT":
        state.filter((i: any) => {
          if (i.id === action.payload.id) {
            i.count = i.count + 1;
          }
          return i;
        });
        const noDuplicateArr = state.filter(
          (i: any) => i.id !== action.payload.id
        );
        return [...noDuplicateArr, action.payload];

      case "DELETE_PRODUCT":
        state.filter((i: any) => {
          if (i.id === action.payload.id) {
            i.count = 1;
          }
          return i;
        });
        return state.filter((item: any) => item.id !== action.payload.id);

      default:
        return state;
    }
  }, []);

  let totalAmount = 0;
  products.forEach(product => {
    totalAmount += product.count * product.price.amount;
  })
  const fixedAmount = totalAmount.toFixed(2);

  const store = useMemo(() => ({ products, dispatch }), [products]);

  return (
    <div className="app">
      <ProductContext.Provider value={{ allData, fixedAmount, store }}>
        <ShoppingList />
        <ShoppingBag />
      </ProductContext.Provider>
    </div>
  );
}

Upvotes: 3

Related Questions