React/Redux fetching data and conditionally render a component - too many re-renders

Viewed 90

I want to fetch data from my backend, change the 'isLoggedIn' state accordingly and therefore render a component conditionally. I just can't get my around it on how to structure it in a way that the component does not render twice.

First in my AuthForm component, there is a login function. It sets a httpOnly cookie for refreshing the JWT token, and and a JWT token directly on the headers.

const login = async () => {
        try {
            const response = await axios.post(props.baseUrl + "/auth/login", {
                email,
                password,
            })

            axios.defaults.headers.common[
                "Authorization"
            ] = `Bearer ${response.data.token}`

            dispatch(toggleLogin(true))
        } catch ({ response }) {
            console.log(response)
        }
    }

Since I want to display the Game UI instead of the AuthForm, I'm dispatching the toggleLogin action.

In the main component, i have a refreshToken function, that checks if the client got a httpOnly cookie and returns a new JWT if it is valid. So that the user is still logged in on a page refresh.

const Tamagotchi = () => {
    const isLoggedIn = useSelector((state) => state.isLoggedIn)
    const dispatch = useDispatch()

    const refreshToken = async () => {
        try {
            const response = await axios.get(baseUrl + "/auth/refresh-token")

            axios.defaults.headers.common[
                "Authorization"
            ] = `Bearer ${response.data.token}`

            dispatch(toggleLogin(true))

            setTimeout(() => {
                refreshToken()
            }, response.data.expiresIn * 1000 - 500)
        } catch ({ response }) {
            if (response.status !== 201) {
                dispatch(toggleLogin(false))
                console.log("Not authorized")
            }
        }
    }

    useEffect(() => {
        refreshToken()
    })

    return (
        <TamagotchiWrapper>
            <TamagotchiView>
                {isLoggedIn ? <TamagotchiUI /> : <AuthForm />}
            </TamagotchiView>
        </TamagotchiWrapper>
    )
}

If the user is logging in, or is refreshing the page, the Tamagotchi component gets rendered twice. The state gets changed because isLoggedIn changes and therefore it renders again and calling the useEffect hook again. How to go about this?

Thanks in advance.

3 Answers

Try to wrap the refreshToken() call inside of useMemo instead of useEffect. useEffect works after the component prints the JSX and useMemo works before it.

useMemo(() => {
  refreshToken()
}, []);

Please provide empty dependency array which means only render during initial loading.

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

The solution was to pass refreshToken as a dependency in useEffect() and wrap the refreshToken() function in a useCallback Hook.

Then pass the refreshToken() function to my AuthForm component and call it on successful login.

This way the refreshToken function gets called correctly when opening the app to authenticate the user, and on successful login to be able to refresh the token.

Related