Home > Software design >  Refactoring useEffect to only require one database call
Refactoring useEffect to only require one database call

Time:11-01

At the moment, I have a component which completes some backend calls to decide when to start displaying the UI.

It's structured like this:

useEffect(() => {
  getData()
 })

const getData = async () => {
  await verifyUser()
  await fetchData()
}

The purpose here, is that verifyUser() is supposed to run first, and in the response to verifyUser(), a user id is provided by the backend.

const verifyUser = async () => {

        if (!localStorage.getItem('auth')) {
            return
        }

        if (localStorage.getItem('auth')) {
            await axios.post("/api/checkAuth", {
                token: JSON.parse(localStorage.getItem('auth'))
            })
            .then((response) => {
                return setUserId(response.data.user_id)
            })
            .catch((err) => {
                console.log(err)
                localStorage.removeItem('auth')
            })
        }
        
}

As a result of this, the fetchData() function is supposed to wait until the verifyUser() function has stopped resolving, so it can use the user id in the database query.

However, at the moment it...

  • Calls once, without the user id
  • Then calls again, with the user id (and therefore resolves successfully)

Here's the function for reference:

const fetchData = async () => {

        console.log("Fetch data called.")
        console.log(userId)
        
        await axios.post("/api/fetch/fetchDetails", {
            user_id: userId
        })
        .then((response) => {
            // Sets user details in here...
            return response
        })
        .then(() => {
            return setFetching(false)
        })
        .catch((err) => {
            console.log(err)
        })
    }

What I'm trying to achieve here is to essentially remove any concurrency and just run the functions sequentially. I'm not 100% sure what the best practice here would be, so some feedback would be appreciated!

CodePudding user response:

Your useEffect is missing a dependency array argument:

useEffect(() => {
  getData()
})

should be:

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

Without that argument, useEffect will run once each time your component renders. With that argument, it will only run once, when the component is first mounted (ie. after the first render).

If you needed it to depend on another variable (eg. user.id isn't defined on load, but is later on) you could put that variable in the dependency array, ie.

useEffect(() => {
  if (!user.id) return;
  getData()
}, [user.id])

This version would run once when the component is mounted, then again if the user.id changes (eg. if it goes from null to an actual number).

CodePudding user response:

In React, the useEffect hook accepts two arguments - the first one is a function (this is the "effect"), and the second one is a dependency array. The simplest useEffect hook looks like this:

useEffect(() => {

}, [])

The above hook has no dependency (because the array is empty), and runs only when the component initially mounts, and then goes silent.

If you don't pass in a dependency array as the second argument, as @machineghost said, the hook will run the "effect" function every time your component re-renders.

Now to your specific problem. You want to run fetchData after verifyUser has resolved its Promise, so you'd add the outcome of verifyUser as a dependency to a separate useEffect hook that calls fetchData. In this case, the outcome is setting userId.

So instead of this:

useEffect(() => {
  getData()
 })

const getData = async () => {
  await verifyUser()
  await fetchData()
}

Do this:

useEffect(() => {
  verifyUser();
 }, []);

useEffect(() => {
  if (userId) { // assuming userId has a false-y value before verifyUser resolved
    await fetchData();
  }
}, [userId])
  • Related