wpengine / faustjs

Faust.js™ - The Headless WordPress Framework
https://faustjs.org
Other
1.41k stars 126 forks source link

useLogin and useAuth do not work together to update `isAuthenticated`/`viewer` #1923

Open bpkennedy opened 1 month ago

bpkennedy commented 1 month ago

Description

In this example code I'm trying to login the user with useLogin and then wait to see them become authenticated by using useAuth. I can confirm after login that a cookie is created in the browser and I see a GET request completed to url http://localhost:3000/api/faust/auth/token?code=1PrSDHzvYuNSANI47ZiP%2BvyRwEbQ8eihr7rq5gI3fpok%2FXY%2FP%2BwsJovxj4aW3tYHmE1PFLx%2FC%2BjnxwJo8TiUvg%3D%3D with output like this:

{
  accessToken: "yAJXMIslG6TucsCZQDyzq3ycLT6VaejoGvUEQrT7KVdChwUzbnt1rKU/2fL1u/HTuxspAdALbpDNc85P2ThRvw=="
  accessTokenExpiration: 1722542440
  refreshToken: "UlS1wyVXj/+XR/6jxavbjjxq9bS8Yy15N/9WWQJxbZ6c0prFKNhta/V2BBgZPTC7CUEQGGING1IPFw5+vJYDqA=="
  refreshTokenExpiration: 1723751740
}

Furthermore, I can soft refresh the browser page and if I were to console.log viewer from useAuth on the next page load then it would recognize the logged in user. So I know that it's successfully logging me in as the user.

The issue is that useAuth is never updating either isAuthenticated or viewer right after a successful login() with useLogin. Here is the Next.js component that demonstrates how I'm trying to get access to isAuthenticated after successful login:

import CloseButton from '@/components/CloseButton'
import GravityForm from '@/components/GravityForm'
import { GRAVITY_FORM_IDS } from '@/lib/constants'
import { useAuth, useLogin } from '@faustwp/core'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'

const MultiStepRegistration = () => {
  const [step, setStep] = useState(1)
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [isLoggingIn, setIsLoggingIn] = useState(false)
  const router = useRouter()

  const { login } = useLogin()
  const { viewer, isAuthenticated } = useAuth()

  useEffect(() => {
    let checkAuthInterval
    if (isLoggingIn) {
      checkAuthInterval = setInterval(() => {
        console.log('Checking authentication status...')
        if (isAuthenticated) {
          console.log('User is now authenticated')
          clearInterval(checkAuthInterval)
          setIsLoggingIn(false)
          setStep(2)
        }
      }, 1000) // Check every second
    }
    return () => {
      if (checkAuthInterval) clearInterval(checkAuthInterval)
    }
  }, [isLoggingIn, isAuthenticated])

  const handleStep1Success = async (queryResult, formData) => {
    const emailField = queryResult.data.submitGfForm.entry.formFields.nodes.find((field) => field.type === 'EMAIL')
    const passwordField = queryResult.data.submitGfForm.entry.formFields.nodes.find((field) => field.type === 'PASSWORD')

    const submittedEmail = formData[emailField.id]
    const submittedPassword = formData[passwordField.id]

    setEmail(submittedEmail)
    setPassword(submittedPassword)

    try {
      setIsLoggingIn(true)
      login(submittedEmail, submittedPassword)
    } catch (error) {
      console.error('Login error:', error)
      setIsLoggingIn(false)
      // Handle login error (e.g., show an error message)
    }
  }

  const handleStep2Success = (queryResult, formData) => {
    // Handle step 2 form submission
  }

  return (
    <main className="flex min-h-full flex-1 flex-col justify-center px-6 lg:px-8 relative">
      <CloseButton onClick={() => router.push('/')} className="absolute top-4 right-4" ariaLabel="Close" />

      <div className="sm:mx-auto sm:w-full sm:max-w-sm">
        <h2 className="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Create New Account</h2>
        <p className="mt-2 text-center text-sm text-gray-600">Step {step} of 2</p>
      </div>

      <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-sm">
        {step === 1 && (
          <GravityForm
            formId={GRAVITY_FORM_IDS.registrationStepOne}
            showLabels={true}
            submitButtonText="Next"
            submittingButtonText="Submitting..."
            onSubmitSuccess={handleStep1Success}
            className="mt-4 space-y-6"
          />
        )}
        {step === 2 && (
          <GravityForm
            formId={GRAVITY_FORM_IDS.registrationStepTwo}
            showLabels={true}
            submitButtonText="Complete Registration"
            submittingButtonText="Submitting..."
            onSubmitSuccess={handleStep2Success}
            className="mt-4 space-y-6"
          />
        )}
        {isLoggingIn && <p className="mt-4 text-center text-sm text-gray-600">Logging in...</p>}
      </div>
    </main>
  )
}

export default MultiStepRegistration

What happens instead is that it will wait forever for isAuthenticated (or viewer) to change, logging 'Checking authentication status...' to the console every second, but it never changes.

Steps to reproduce

See description

Additional context

No response

@faustwp/core Version

^3.0.1

@faustwp/cli Version

^3.0.2

FaustWP Plugin Version

1.3.2

WordPress Version

6.6.1

Additional environment details

No response

Please confirm that you have searched existing issues in the repo.

bpkennedy commented 1 month ago

Here is a minimal example. Create a user with email test7@example.com and password pass. Then try this (ensure you've cleared your browser cache):

import { useAuth, useLogin } from '@faustwp/core'
import { useEffect, useState } from 'react'

const Example = () => {
  const { login } = useLogin()
  const { viewer, isAuthenticated } = useAuth()
  const [isLoggingIn, setIsLoggingIn] = useState(false)

  useEffect(() => {
    console.log('Auth state changed - isAuthenticated:', isAuthenticated, 'viewer:', viewer)
  }, [isAuthenticated, viewer])

  useEffect(() => {
    let checkAuthInterval
    if (isLoggingIn) {
      checkAuthInterval = setInterval(() => {
        console.log('Checking authentication status...')
        console.log('isAuthenticated:', isAuthenticated)
        console.log('viewer:', viewer)
        if (isAuthenticated) {
          console.log('User is now authenticated')
          setIsLoggingIn(false)
          clearInterval(checkAuthInterval)
        }
      }, 1000) // Check every second
    }
    return () => {
      if (checkAuthInterval) clearInterval(checkAuthInterval)
    }
  }, [isLoggingIn, isAuthenticated, viewer])

  const handleLogin = async () => {
    try {
      setIsLoggingIn(true)
      console.log('Attempting login...')
      await login('test7@example.com', 'pass')
      console.log('Login function called')
    } catch (error) {
      console.error('Login error:', error)
      setIsLoggingIn(false)
    }
  }

  return (
    <div>
      <button onClick={handleLogin}>
        {isLoggingIn ? 'Logging in...' : 'Click Me to Login'}
      </button>
      <p>Authentication status: {isAuthenticated ? 'Authenticated' : 'Not authenticated'}</p>
      {viewer && <p>Logged in as: {viewer.username}</p>}
    </div>
  )
}

export default Example

This useEffect is never triggered:

  useEffect(() => {
    console.log('Auth state changed - isAuthenticated:', isAuthenticated, 'viewer:', viewer)
  }, [isAuthenticated, viewer])