0

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?

DᴀʀᴛʜVᴀᴅᴇʀ
  • 7,681
  • 17
  • 73
  • 127
  • 2
    This has nothing to do with `.map()`... Trying to update state *at all* during a render is a bad idea, and leads to exactly this error. (State update triggers re-render, render triggers state update, endlessly.) Taking a step back... Why do you think you need/want to update state during the render? What are you trying to achieve? – David Aug 08 '22 at 21:38
  • 1
    Can you explain in words when a link should be active and when it should not be? – Codebling Aug 08 '22 at 21:39
  • @David Because it will be used to update `Context` to resolve this: [With Styled Components how to style parent based on child having aria-current page or a particular className?](https://stackoverflow.com/questions/73213817/with-styled-components-how-to-style-parent-based-on-child-having-aria-current-pa). So far this was the only solution I could think of to resolve my other question. – DᴀʀᴛʜVᴀᴅᴇʀ Aug 08 '22 at 21:40
  • 1
    @DᴀʀᴛʜVᴀᴅᴇʀ: I don't really follow the reasoning there, or how trying to style a component has led to trying to infinitely re-update state. You definitely *don't* want to update state during a render. So why are you trying to? What's the goal? It sounds like we're well into the Y part of an XY Problem, perhaps a couple of Y's deep. Perhaps you could reduce the overall problem to a runnable [mcve] which demonstrates the goal? – David Aug 08 '22 at 21:44
  • @David Only thing I could think of was update state to then pass it as a prop to the styled component based on wether the link was `true` or `false`. – DᴀʀᴛʜVᴀᴅᴇʀ Aug 08 '22 at 21:46
  • @David I'm not sure how it's not miminal when I thought it was clear I wanted to update state in a map on wether `pathname.toString().includes(link)` is true? – DᴀʀᴛʜVᴀᴅᴇʀ Aug 08 '22 at 21:48
  • Sometimes react renders multiple times when it encounters errors. You should look in browser console logs for errors as well. – kta Aug 22 '23 at 08:13

3 Answers3

0

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

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

0

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)}
      >
Jordan Daniels
  • 4,896
  • 1
  • 19
  • 29
0

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>
    );
}
Jacob Smit
  • 2,206
  • 1
  • 9
  • 19