Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 233
Leff Avatar asked Nov 06 '22 16:11

Leff


1 Answers

Every useEffect callback will be invoked on first mount. You should include a simple if statement to ensure an error is set before running your error handling logic.

  useEffect(() => {
    if(error) {
      console.log(error)
      //window.location.href = '/login'
    }
  }, [error])

There is likely an issue with the CORS configuration of your API.

Access-Control-Allow-Origin response header must be set to the origin of your react app (it cannot be * for credentialed requests) and Access-Control-Allow-Credentials must be true. Failing to include them will result in an opaque response.

https://fetch.spec.whatwg.org/#cors-protocol-and-credentials

like image 69
Andrew Gillis Avatar answered Nov 15 '22 06:11

Andrew Gillis