Search code examples
reactjsnext.jsfetch-apiswr

Too many re-renders error when making request to API route on click with SWR


When I click on a button inside a component I send back to the Page an id via function const readUrl. Now I'm trying to send client-side that id to an /api/dat/[id].js route where inside it basically searches for that id within mongodb and res.send back to the Page the JSON result.

So via SWR, I'm dealing with the API route client-side and I use two useState to deal with id from a button click and result from API to pass it to the react DOM.

The issue is I get:

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

This is my api/dat/[id].js:

import { connectToDatabase } from "../../utils/mongodb"
    
export default async function ({ query: { id } }, res) {   
    const { db } = await connectToDatabase();
    const dataID = id

    const foundData = /* query to mongodb.toArray() */

    if (foundData.length) {
        res.status(200).send({ data: foundData[0] })
    } else {
        res.status(404).json({ message: `Data ${id} not found.` })
    }
    
    res.end()
}

This is the code in my page:

const [urlNow, setUrlNow] = useState(null);
const [urlData, setUrlData] = useState(null);

const readUrl = (url) => {
  setUrlNow(url)
  return true
}

const fetcher = (...args) => fetch(...args).then(res => res.json())
const { data, error } = useSWR(readUrl ? `/api/dat/${urlNow}` : null, fetcher)

// if (error) return <div>{error.message}</div>
// if (!data) return <div>Loading...</div>
if (data) setUrlData(data.data)

return (
     ...
       {urlData
          ? <Component data={urlData} />
          : <ComponentWithButton readUrl={readUrl} />
       }

useSWR as it is set above fires up only if function readUrl returns true, as SWR docs say, so I think it should fetch the API route only if the button gets clicked, at least that was the idea.


Solution

  • You should replace the condition in useSWR() to use urlNow rather than the readUrl function.

    Some states/variables can also be cleaned up to make the code simpler, and avoid the infinite rerender caused by setUrlData(data.data).

    const [urlNow, setUrlNow] = useState(null);
    
    const readUrl = (url) => {
        setUrlNow(url)
    }
    
    const fetcher = (...args) => fetch(...args).then(res => res.json())
    const { data, error } = useSWR(urlNow ? `/api/dat/${urlNow}` : null, fetcher)
    
    // if (error) return <div>{error.message}</div>
    // if (!data) return <div>Loading...</div>
    
    return (
        {data
            ? <Component data={data} />
            : <ComponentWithButton readUrl={readUrl} />
        }
        //...
    )