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

I think i've been tried all possible possibilities but its not working. I wanna make dynamic menu (i know its not correct database fiction for this but i wrote for learn).

I'll create json for this plan. I can't edit json file coz i wanna try regenerate json data from json.

I can say it Next.js is forced me a little.

This is my code block:

import {useEffect, useState } from "react";

export default async function MenuCreator() {
    const [menu,setMenu] = useState([]); 
    const [subMenu,setSubMenu] = useState([]);
    const [menu_f,setMenu_F] = useState([]);
    useEffect(()=>{
        fetch("http://localhost:8000/menus").then((res)=>{
            if(!res.ok) throw Error("error");
            return res.json();
        }).then((data)=>{
            setMenu_F(data);
        });
    },[])

        menu_f.forEach((main)=>{
            
            if(main.main_menu === null)
            {
                menu_f.forEach((sub)=>{
                    if(sub.main_menu === main.id)
                        setSubMenu([...subMenu,{id:sub.id,menu_name:sub.menu_name,main_menu:sub.main_menu,menu_link:sub.menu_link}]);
                })
                setMenu([...menu,{
                    id:main.id,
                    menu_name:main.menu_name,
                    menu_icon:main.menu_icon,
                    menu_link:main.menu_link,
                    subMenus:subMenu
                }]);
                setSubMenu([]);
            }
        })
    return {menu};
}

and json file

{
    "quick_links":[
        {
            "id":1,
            "menu_id":1
        },
        {
            "id":2,
            "menu_id":2
        }
    ],
    "menus":[
        {
            "id":1,
            "menu_name":"dashboard",
            "menu_icon":"fa-gauge",
            "main_menu":null,
            "menu_link":"/"
        },
        {
            "id":2,
            "menu_name":"receipt",
            "menu_icon":"fa-file-lines",
            "main_menu":null,
            "menu_link":"/dashboard"
        },
        {
            "id":3,
            "menu_name":"buy",
            "menu_icon":"fa-cart-plus",
            "main_menu":null,
            "menu_link":"/buy"
        },
        {
            "id":4,
            "menu_name":"users",
            "menu_icon":"fa-user",
            "main_menu":null,
            "menu_link":"/users"
        },
        {
            "id":5,
            "menu_name":"user list",
            "menu_icon":null,
            "main_menu":4,
            "menu_link":"/user-list"
        },
        {
            "id":6,
            "menu_name":"add user",
            "menu_icon":null,
            "main_menu":4,
            "menu_link":"/add-user"
        }
    ]
}

Upvotes: -1

Views: 1343

Answers (2)

HsuTingHuan
HsuTingHuan

Reputation: 625

Summary

This is because you are in a infinite loop

When the code carry out setState, it will re-render again
So you need tell the react component only carry out setState when state has changed.

Move your forEach loop into a useEffect(), and add a second parameter [menu_f] means that execute only when the menu_f state mutated like

useEffect(()=>{} ,[menu_f])

a modified example from question

useEffect(() => {
 menu_f.forEach((main)=>{    
            if(main.main_menu === null)
            {
                menu_f.forEach((sub)=>{
                    if(sub.main_menu === main.id)
                        setSubMenu([...subMenu,{id:sub.id,menu_name:sub.menu_name,main_menu:sub.main_menu,menu_link:sub.menu_link}]);
                })
                setMenu([...menu,{
                    id:main.id,
                    menu_name:main.menu_name,
                    menu_icon:main.menu_icon,
                    menu_link:main.menu_link,
                    subMenus:subMenu
                }]);
                setSubMenu([]);
            }
        })
}, [menu_f]);

Other Example

a fail example in your case

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

        <div id="root"></div>

        <script type="text/babel">
          function App () {
            const [state, setState] = React.useState("No");
            
            // ***
            // Error happened here 
            // Need change this by React.useEffect() with parameter [state]
            // And setState in other function or event
            setState("Yes");
            // ***
            
            const handleClick = () = {}
            
            return <div>
              <button onClick={handleClick}>Click to setState</button>
              {state}
            </div>
          }
        </script>

        <script type="text/babel">
            ReactDOM.render(
                <App></App>
                , document.getElementById("root"));
        </script>

a successful example

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

    <div id="root"></div>

    <script type="text/babel">
      function App () {
        const [state, setState] = React.useState("No");

        React.useEffect(() => {
          // other stuff...
        }, [state])
        
        const handleClick = () => {
          if (state !== "Yes") {
            setState("Yes")
          } 
          else {
            setState("No")
          }
        }
        
        return <div>
          <button onClick={handleClick}>Click to setState</button>
          {state}
        </div>
      }
    </script>

    <script type="text/babel">
        ReactDOM.render(
            <App></App>
            , document.getElementById("root"));
    </script>

Other

And your main_f.forEach's code block is weird, please consider @Nicolas Menettrier's suggestion

Upvotes: 1

Nicolas Menettrier
Nicolas Menettrier

Reputation: 1679

You're updating the state at every render inside your menu_f.forEach, as a quick fix you can put everything inside a useEffect that listen on menu_f change like this:

useEffect(() => {
        menu_f.forEach((main)=>{
            if(main.main_menu === null)
            {
                menu_f.forEach((sub)=>{
                    if(sub.main_menu === main.id)
                        setSubMenu([...subMenu,{id:sub.id,menu_name:sub.menu_name,main_menu:sub.main_menu,menu_link:sub.menu_link}]);
                })
                setMenu([...menu,{
                    id:main.id,
                    menu_name:main.menu_name,
                    menu_icon:main.menu_icon,
                    menu_link:main.menu_link,
                    subMenus:subMenu
                }]);
                setSubMenu([]);
            }
        })
}, [menu_f])

When you do a setMenu/setSubMenu it triggers a rerender so it'll go again in your forEach loop and do the setMenu/setSubMenu again and again doing an infinite loop.

EDIT: I guess your code will not work as expected, in the if (main.main_menu === null) you're doing a lot of unexpected state manipulation. setSubMenu will not change the subMenu variable instantly so the setMenu that is using subMenu will have an undefined value instead of what you want.

What you can do to avoid this:

if (main.main_menu === null) {
   const subMenuNotState = menu_f.find(sub => sub.main_menu === main.id);

   if (subMenuNotState) {
     setMenu([...menu,{
        id:main.id,
        menu_name:main.menu_name,
        menu_icon:main.menu_icon,
        menu_link:main.menu_link,
        subMenus:subMenuNotState
     ]);
   }
}

Upvotes: 1

Related Questions