Home > Net >  In React how to prevent too many renders on a map?
In React how to prevent too many renders on a map?

Time:08-09

I'm doing something wrong in my approach trying to set a className based on the useState from a sub navigation map.

(code stripped):

const [activeLink, setActiveLink] = useState(0)

// removed code

{items.map((item, key) => {
  const { title, link } = item

  return (
    <React.Fragment key={key}>
      <Link
        to={`/${link}`}
        className={activeLink === 1 ? 'active' : ''}
      >
        {title}
      </Link>
    </React.Fragment>
  )
})}

Attempt 1

{items.map((item, key) => {
  const { title, link } = item
  let testLink = null
  testLink = pathname.toString().includes(link)
  if (testLink === true && activeLink === 0) setActiveLink(1)
  if (testLink === false && activeLink === 1) setActiveLink(0)

  return (
    <React.Fragment key={key}>
      <Link
        to={`/${link}`}
        className={activeLink === 1 ? 'active' : ''}
      >
        {title}
      </Link>
    </React.Fragment>
  )
})}

Throws the error of:

Too many re-renders. React limits the number of renders to prevent an infinite loop.

Attempt 2

 const handleactive = link => (pathname.toString().includes(link) ? 1 : 0)
  useEffect(() => {
    if (activeLink === false && handleactive() === 1) return setActiveLink(1)
    return setActiveLink(0)
  }, [activeLink])

Attempt 3

const handleactive = link => {
  if (activeLink === 0 && pathname.toString().includes(link) === true) return setActiveLink(1)
  return setActiveLink(0)
}


{items.map((item, key) => {
  return (
    <React.Fragment key={key}>
      <Link
        to={`/${link}`}
        className={activeLink === 1 ? 'active' : ''}
        handleactive={handleactive(link)}
      >
        {title}
      </Link>
    </React.Fragment>
  )
})}

Research

What am I doing wrong and how can I, in a map, update the state?

CodePudding user response:

You can't call the setState function of useState during a render.

The map is run on each render therefore each render there is a chance (conditions dependent) that you are calling setActiveLink.

If you wish to update state per item within a map, you probably want to create an extra component for the Links.

The links can then either keep their own state or set the parent state via callback functions passed to them from the parent.

// Map in existing parent component
{items.map((item, key) => {

  return (
    <NavLink 
        key={key} 
        {...item} 
        pathname={pathname} 
    />
  )
})}

// New component
const NavLink = ({title, link, pathname}) => {
    const active = useMemo(() => {
        return pathname.toString().includes(link)
    }, [pathname, link]);

    return (
        <Link
            to={`/${link}`}
            className={active ? 'active' : ''}
        >
            {title}
        </Link>
    );
}

CodePudding user response:

You don’t have to execute and return the state method.

remove the return and execute only, that can solve the issue

CodePudding user response:

What is happening, you are calling setActiveLink inside of your map, which triggers a render. After that render, the code inside the map gets called, calling setActiveLink again, causing another re-render, which eventually causes an infinite loop.

You need to put your setActiveLink into an onClick that only calls it once.

Example 3:

      <Link
        to={`/${link}`}
        className={activeLink === 1 ? 'active' : ''}
        onClick={() => handleactive(link)}
      >
  • Related