Search code examples
reactjsjwtfetchreact-devtools

React - useEffect running even when there was no change in state variable


I have an endpoint in my kotlin app that looks like this:

    either.eager<String, Unit> {
      val sessionAndCookieUser = commonAuth.decryptCookieGetUser(getCookie(context), ::userTransform).bind()
      val user = sessionAndCookieUser.session.user
      val ctx = Ctx(ds, SystemSession, conf)
      val dbUser = getUserEither(ctx, user.id).bind()

      val signatureAlgorithm = SignatureAlgorithm.HS256
      val signingKey = SecretKeySpec(conf.get(ZendeskJWTSecret).toByteArray(), signatureAlgorithm.jcaName)

      val iat = Date(System.currentTimeMillis())
      val exp = Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)

      val token = Jwts.builder()
          .claim("name", dbUser.name)
          .claim("email", dbUser.email)
          .setIssuer(conf.get(StreamAppName))
          .setIssuedAt(iat)
          .setExpiration(exp)
          .signWith(signingKey, signatureAlgorithm)
          .compact()

      context.setResponseCode(StatusCode.OK)
          .setResponseType("application/json")
          .send(jsonObject("token" to token).toString())
    }.mapLeft {
      context.setResponseCode(StatusCode.UNAUTHORIZED)
    }

I am setting a response where I should send a jsonObject if a user is authenticated or UNAUTHORIZED if the user is not authenticated. When I am testing this endpoint in a browser I just get status unknown for that request - when I was debugging the backend, otherwise I get 200 with no response data. If I test it in postman I get json as a response. I see that token is being built and everything looks good on the backend side, but then response is not being loaded in the browser.

I am fetching it like this from react:

export const fetchGet = (uriPath: string) => 
  fetch(fullUrl(uriPath), {
    method: 'GET',
    credentials: 'include'
})

useEffect(() => {
    console.log('got here')
    fetchGet('/auth/token')
      .then(res => {
        console.log('res ', res)
       return res.json()
      })
      .then(res => {
        console.log('res.json ', res)
        return res.ok ? setJwtToken(res.token) : Promise.reject(res.statusText)
      })
      .catch(error => {
        console.log('err ', error)
        setError(error.toString())
      })
  }, [])

In the console I can only see 'got here' being logged, nothing else, and frontend crushed with an error:

DevTools failed to load source map: Could not load content for data:application/json;charset=utf-8;base64, longTokenString...: Load canceled due to reload of inspected page

What am I doing wrong here?

Updated

I found an issue here, I had 2 more useEffect functions, and they were redirecting before I had a result. I am not sure why was the useEffect function where I am passing the error state variable running when there was no change from initial state?

Here is the full code:

const [jwtToken, setJwtToken] = useState(null)
const [error, setError] = useState(null)

useEffect(() => {
    fetchGet('/auth/token')
      .then(async res => {
        const data = await res.json()
        if (!res.ok) {
          const error = data?.message || res.statusText
          return Promise.reject(error)
        }
        return data
      })
      .then(({token}) => setJwtToken(token))
      .catch(err => {
        console.log('err ', err)
        setError(err.toString())
      })
  }, [])

  useEffect(() => {
    if (jwtToken) {
      // window.location.href = `/mypage.com?access/jwt?jwt=${jwtToken}&return_to=`
      console.log(jwtToken)
    }
  }, [jwtToken])
  useEffect(() => {
    console.log(error)
    //window.location.href = '/login'
  }, [error])

Update nr. 2:

const [jwtToken, setJwtToken] = useState('')
  const { search } = useLocation()

  useEffect(() => {
    fetchGet('/auth/token')
      .then(async res => {
        const data = await res.json()
        if (!res.ok) {
          const error = data?.message || res.statusText
          return Promise.reject(error)
        }
        return data
      })
      .then(({token}) => setJwtToken(token))
      .catch(() => window.location.href = '/login')
  }, [])

  useEffect(() => {
    const params = new URLSearchParams(search)
    const returnTo = params.get('return_to') ? `&return_to=${params.get('return_to')}` : ''
    jwtToken !== '' ? window.location.href = `${url}/jwt?jwt=${jwtToken}${returnTo}` : null
  }, [jwtToken])

  return <p>Authenticating ...</p>

I have removed unnecessary error useEffect function, but now I get:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I get this warning and it is also not redirecting after the token is fetched. What am I doing wrong this time around?


Solution

  • Here is my completed answer. The main problem here is using useEffect incorrectly, especially with objects in the dependency array.

    Let's talk about this code first

    useEffect(() => {
      // TODO something with error
    }, [error]);
    

    Because error is an object and React useEffect use shallow comparison as you can see in this question. It will make the code inside that useEffect will run forever.

    Next part, you get warnings because your use of redirect is not in the right way. Just remove that useEffect and it should work.

    The reason why is, when we have an error, your code in your catch should run. Beside that, jwtToken will be changed at that time too. It will make your app redirected before the rendering process is completed.