skip to Main Content

I have an unregistered user that works with my application, getting some progress. I keep this progress in local storage. Then, when the user decides to register, I send a PUT request to sync his progress with DB.

For that I track its status and when it’s become authenticated and the progress parameter empty, I send a PUT request, but the problem is it sends dozens of PUT requests to update the progress instead of one.

'use client'
import { useSession, signIn, signOut, useUser } from 'next-auth/react'
import { use, useContext, useEffect, useState } from 'react'
import AppContext from '../../utils/AppContext'

const updateProfile = async (email, prog) => {
  await fetch(`http://localhost:3000/api/users/${email}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      progress: prog,
    }),
  })
}

export default function Component() {
  const { progressArr, setProgressArr } = useContext(AppContext)
  const { data: session, status } = useSession()

  function updateUser() {
    use(updateProfile(session.user.email, progressArr))
  }

  if (status === 'authenticated') {
    if (!session.user.progress) {
      updateUser()
    }
  }

  if (session) {
    return (
      <>
        Signed in as {session.user.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    )
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  )
}

P.S. If it should be done in another way, share your ideas with me please.

5

Answers


  1. Your updateUser is called on "component render" so every time when your application changes state or any of parent components change state updateUser gets called.

    Since this code introduces a side-effect for your component you can try to put this code into useEffect hook like:

    useEffect(() => {
      if (status === 'authenticated') {
        if (!session.user.progress) {
          updateUser()
        }
      }
    }, status);
    

    However I won’t recommend to keep components responsible for maintaining application state and handling side effects.

    My recommendation would be to onboard you app with app-state management framework, like redux and move your data submission logic to middleware.
    This way your data flow would look like:

    1. User makes progress -> component dispatches event about progress made
    2. Middleware picks up the event and based on authentication state either saves it to local storage or sends it to backend.
    Login or Signup to reply.
  2. It seems to me that updateUser() is getting called multiple times by accident.
    You could try to keep track of your request by adding something like updateInProgress as a state and set it to true once updateUser() is triggered and set it to false afterwards again.
    After you’ve done this, you could just return if there is already an update in progress:

    async function updateUser() {
        if (updateInProgress) {
          return
        }
    
    Login or Signup to reply.
  3. That could be the cause of multiple render cycles. Moving logic to useEffect might be

    
      useEffect(() => {
        if (status === 'authenticated' && session?.user?.email && !session.user.progress) {
          updateProfile(session.user.email, progressArr)
        }
      }, [progressArr, status, session?.user?.email])
    
    Login or Signup to reply.
  4. There is useEffect hook to send a single request to update the progress of the user. useEffect hook is called when the session, status, or progressArr changes. If the user is authenticated and their progress is undefined, we call the updateProfile

    Try this code. I think it will help you

    'use client'
    import { useSession, signIn, signOut, useUser } from 'next-auth/react'
    import { use, useContext, useEffect, useState } from 'react'
    import AppContext from '../../utils/AppContext'
    
    const updateProfile = async (email, progressArr) => {
      await fetch(`http://localhost:3000/api/users/${email}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          progress: progressArr,
        }),
      })
    }
    
    export default function Component() {
      const { progressArr, setProgressArr } = useContext(AppContext)
      const { data: session, status } = useSession()
    
      useEffect(() => {
        if (status === 'authenticated' && session.user.progress === undefined) {
          updateProfile(session.user.email, progressArr)
        }
      }, [session, status, progressArr])
    
      if (session) {
        return (
          <>
            Signed in as {session.user.email} <br />
            <button onClick={() => signOut()}>Sign out</button>
          </>
        )
      }
      return (
        <>
          Not signed in <br />
          <button onClick={() => signIn()}>Sign in</button>
        </>
      )
    }
    
    Login or Signup to reply.
  5. Can you please try below code:

    export default function Component() {
      const { progressArr, setProgressArr } = useContext(AppContext)
      const { data: session, status } = useSession()
    
      useEffect(() => {
        if (status === 'authenticated' && !session.user.progress) {
          updateProfile(session.user.email, progressArr)
        }
      }, [progressArr])
    
      if (session) {
        return (
          <>
            Signed in as {session.user.email} <br />
            <button onClick={() => signOut()}>Sign out</button>
          </>
        )
      }
      return (
        <>
          Not signed in <br />
          <button onClick={() => signIn()}>Sign in</button>
        </>
      )
    }
    

    using the useEffect hook to run the updateUser() by adding [progressArr] as the second argument to useEffect.

    Note:
    In theory useContext and useSession hooks to render sepcific contents (data) depending on authenticated user. It nice to implementing over authentication flow. useEffect hook is used to update the user’s profile with the progressArr data if the user is authenticated and does not have a progress field in their profile. The updateProfile function is likely used to make an API call to update the user’s profile.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search