2

I created a route in my Next.JS app on Firebase and though I intended to be very careful, I get a problem.

First of all here is the code (page.tsx) for the route:

'use client';

import React, {useState,useEffect} from "react";
import firebase from "../../firebase/initFirebase";
import DrillManage from '../components/drillMng'

export default async function MemberPage() {
  const [memberID, setMemberID] = useState("");
  const [userID, setUserID] = useState("");

    if (typeof window !== "undefined") {
        const memberID = window.location.pathname.substring(1)
        setMemberID(memberID)
    } else console.log('window.location is UNDEFINED')

    useEffect(() => {
        let dbRef = firebase.database().ref('Members')
        dbRef.child(memberID)
        .once('value', (snapshot) => {
            const usrID = JSON.parse(JSON.stringify(snapshot.child('UserID')))
            setUserID(usrID)
        })
    }, [memberID]);

    return (
        <DrillManage usrID={userID} />
    )
}

And this is what I see in my web browser:

Unhandled Runtime Error

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

I don't see in my code anything which could give raise to some infinite loop.

But I may be wrong. Can anyone spot something I am doing the wrong way? And let me know?

I have also tried the following code, but for some reason that I do not catch userID is never set.

'use client';

import React, {useState,useEffect} from "react";
import firebase from "../../firebase/initFirebase";
import DrillManage from '../components/drillMng'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default async function MemberPage() {
    const [userID, setUserID] = useState("");
    const pathname = usePathname().substring(1)
    const [memberID, setMemberID] = useState(pathname);
    console.log('pathname=',pathname)
    console.log('memberID=',memberID)

    useEffect(() => {
        let dbRef = firebase.database().ref('Members')
        dbRef.child(memberID)
        .once('value', (snapshot) => {
            const usrID = JSON.parse(JSON.stringify(snapshot.child('UserID')))
            console.log('usrID=',usrID)
            setUserID(usrID)
        })
    }, [pathname,memberID]);

    return (
        <DrillManage usrID={userID} />
    )
}
Michel
  • 10,303
  • 17
  • 82
  • 179

3 Answers3

2

You're calling a state setter (setMemberId()) during render. That's big React no-no; it will trigger a re-render and enter an infinite loop.

Since you're using Next.js, you should rename your page to [memberId].js and extract the value from the router

const { query: { memberId } } = useRouter();
const [userID, setUserID] = useState("");

useEffect(() => {
  let dbRef = firebase.database().ref("Members");
  dbRef.child(memberID).once("value", (snapshot) => {
    // not sure why you'd need JSON.parse/JSON.stringify
    setUserID(snapshot.child("UserID"));
  });
}, [memberId]);

See https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes

For information on deploying a Next.js app to Firebase Hosting, see https://firebase.google.com/docs/hosting/frameworks/nextjs


If you're having issues with dynamic routes, you can still extract the pathname from the router object

const { pathname } = useRouter();

You can then use your original logic (or something similar) to extract the memberId.

Phil
  • 157,677
  • 23
  • 242
  • 245
  • Well, the last time I tried to use dynamic-routes on Firebase I spent a lot of time and failed (though it worked with no problem on Heroku with the same code). So I know what you do is the right way, but it may not work on Firebase. – Michel Jun 30 '23 at 06:55
  • Do you mean Firebase Hosting? See https://firebase.google.com/docs/hosting/frameworks/nextjs – Phil Jun 30 '23 at 06:59
  • Yes I am using Realtime database and Firebase Hosting. – Michel Jun 30 '23 at 07:07
  • At one point I gave up after posting this https://stackoverflow.com/questions/76370307/something-not-working-with-dynamic-routes-in-next-js-13-4-on-firebase and spending a lot of time. At the time I was able to make my code work on Heroku with no issue. I may want to give it a second try, but I am cautious. – Michel Jun 30 '23 at 07:12
  • @Michel can't say I've ever tried deploying Next.js to Firebase Hosting so I understand your frustration. I've added another option you might try using. – Phil Jun 30 '23 at 07:21
  • @Phil_is_on_strike. I am trying to follow your suggestions (see the code addition I made at the end of the post), but it is still not working as I expect. – Michel Jun 30 '23 at 09:20
2

There’s one more issue with your code - you declared your client component as async. Async components are currently supported only on the server, and that can also cause infinite re-renders in Next.js. You should remove the async keyword.

Igor Danchenko
  • 1,980
  • 1
  • 3
  • 13
1

Explanation: The value of the following condition remains true

typeof window !== "undefined"

Which is why the following block of code which is inside the if is getting executed

const memberID = window.location.pathname.substring(1)
setMemberID(memberID)

setMemberID changes the state memberID and the component re-renders, and the if block is executed again in the next render and changes the state again, and the component has to re-render every time the state changes. Which is why it is creating an infinite loop.

Solution: Change the following code

if (typeof window !== "undefined") {
    const memberID = window.location.pathname.substring(1)
    setMemberID(memberID)
} else console.log('window.location is UNDEFINED')

to the below code

useEffect(() => {
    if (typeof window !== "undefined") {
        const memberID = window.location.pathname.substring(1)
        setMemberID(memberID)
    } else console.log('window.location is UNDEFINED')
}, [window]);

Edit: If you are using react-router-dom in your project, then you can use

import { useLocation } from 'react-router-dom';

and inside the component you can change the code to this

const location = useLocation();
useEffect(() => {
    if (typeof location !== "undefined") {
        const memberID = location.pathname.substring(1)
        setMemberID(memberID)
    } else console.log('location is UNDEFINED')
}, [location]);