Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useMemo hook not working with map elements

I am using useMemo hook to render map items.I added items parameter to useMemo hook, based on items change it will render. But changing loading state and items change, Item custom component rendering twice. Am i doing any mistake on using useMemo hook, please correct me.

Home:

import React, { useState, useEffect, useMemo } from "react";
import Item from "./Item";

const array = [1];
const newArray = [4];

const Home = () => {
  const [items, setItems] = useState(array);
  const [loading, setLoading] = useState(false);
  const [dataChange, setDataChange] = useState(1);

  const renderItems = (item, index) => {
    return (
      <div key={item}>
        <Item id={item}></Item>
      </div>
    );
  };
  useEffect(() => {
    if (dataChange === 2) {
      setLoading(true);
      setTimeout(() => {
        setLoading(false);
        setItems(newArray);
      }, 3000);
    }
  }, [dataChange]);

  const memoData = useMemo(() => {
    return <div>{items.map(renderItems)}</div>;
  }, [items]);

  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <input
        onClick={() => {
          setDataChange(2);
        }}
        style={{ height: 40, width: 100, margin: 20 }}
        type="button"
        value="ChangeItem"
      ></input>
      <div>{loading ? <label>{"Loading"}</label> : <div>{memoData}</div>}</div>
    </div>
  );
};
export default React.memo(Home);

Item:

import React,{useEffect} from "react";
const Item = (props) => {
  console.log("props", props);
useEffect(() => {
// call api with props.id
 }, [props]);
  return <div>Hello world {props.id}</div>;
};
export default React.memo(Item);

Result: first time : props {id: 1}

After click : props {id: 1} props {id: 4}

like image 337
skyshine Avatar asked Sep 24 '21 10:09

skyshine


People also ask

Does useMemo work with objects?

If you want to optimize useMemo you can use object properties in the useMemo dependency array.

Should I use useMemo everywhere?

Why not use useMemo everywhere then? In short, it's not a free performance optimisation. There's an additional cost (memory usage, for one) incurred when setting up useMemo , that can very quickly outweigh the performance benefit of remembering every single function's possible value.

In which situation would you use useMemo () in React?

React has a built-in hook called useMemo that allows you to memoize expensive functions so that you can avoid calling them on every render. You simple pass in a function and an array of inputs and useMemo will only recompute the memoized value when one of the inputs has changed.

Does useMemo cause re render?

useCallback and useMemo for props don't prevent re-renders by themselves. Only when every single prop and the component itself are memoized, then re-renders can be prevented.

What arguments does the usememo () hook accept?

In the above example, the useMemo () hook accepts two arguments: 1 The first is a callback function that will run the function to be memoized 2 The second is a dependency array that will be observed by the hook More ...

Is usememo a react function or a react hook?

React Hook "React.useMemo" is called in function "getCols" which is neither a React function component or a custom React Hook function Why isn't it a React function?

How do I use `usememo`?

`useMemo` allows you to memoize the results of a function, and will return that result until an array of dependencies change. <!-- In this example, a component receives a list of widgets.

How do I use usememo with empty dependencies?

If an empty dependencies array [] is provided, useMemo will only execute the function once on the very first render of the component, while referring to the memoized value for the remainder of the component’s lifecycle. useMemo is especially useful when you are dealing with potentially large lists of items.


1 Answers

There are a few things which are not right in the code above.

  • key should be passed to the parent element in an array iteration - in your case the renderItems should pass the key to the div element
  • you are turning off the loading state before updating the items array, switching the two setState expressions will resolve your case most of the time although setState is an async function and this is not guaranteed
  • if a constant or a function is not tightly coupled to the component's state it is always best to extract it outside the component as is the case with renderItems

Here's why there is one more console.log executed enter image description here

  • also should keep in mind that memoization takes time and you would want to keep it as efficient as possible hence you can totally skip the useMemo with a React.memo component which takes care of the array because it is kept in the state and it's reference won't change on rerender if the state remains the same
    const array = [1];
    const newArray = [4];
    
    const Home = () => {
      const [items, setItems] = useState(array);
      const [loading, setLoading] = useState(false);
      const [dataChange, setDataChange] = useState(1);
    
      useEffect(() => {
        if (dataChange === 2) {
          setLoading(true);
          setTimeout(() => {
            setItems(newArray);
            setLoading(false);
          }, 3000);
        }
      }, [dataChange]);
    
      return (
        <div style={{ display: "flex", flexDirection: "column" }}>
          <input
            onClick={() => {
              setDataChange(2);
            }}
            style={{ height: 40, width: 100, margin: 20 }}
            type="button"
            value="ChangeItem"
          ></input>
          <div>
            {loading ? <label>{"Loading"}</label> : <ItemsMemo items={items} />}
          </div>
        </div>
      );
    };
    
    const renderItems = (item) => {
      return (
        <span key={item} id={item}>
          {item}
        </span>
      );
    };
    
    const Items = ({ items }) => {
      console.log({ props: items[0] });
    
      return (
        <div>
          Hello world <span>{items.map(renderItems)}</span>
        </div>
      );
    };
    
    const ItemsMemo = React.memo(Items);

UPDATE

This codesandbox shows that useMemo gets called only when the items value changes as it is supposed to do.

enter image description here

like image 101
Kia Kaha Avatar answered Oct 08 '22 12:10

Kia Kaha