Search code examples
javascriptreactjstypescriptredux-toolkit

Redirect after register React + Redux Toolkit + async thunk


I am writing a small application with registration and authorization logic. I managed to register. If successful, I fill in all the states as it should be, and if something goes wrong, I get the isError = true status and the error text itself.

The problem is that I want to redirect the user to another page ONLY IF the registration is successful.

I use: React + TS + Redux-toolkit with createAsyncThunc + react-router-dom

react-hook-form + Zod

My register component

import clsx from 'clsx'
import { FC } from 'react'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { useNavigate } from 'react-router-dom'

import { useAppDispatch, useAppSelector } from '../../store/hooks'
import { registerUser } from '../../store/slices/userSlice'

import { FormInput } from '../../components/FormInput/FormInput'
import { Button } from '@gravity-ui/uikit'

import { registerSchema, TRegisterSchema } from '../../models/AuthSchema'

import styles from './RegisterForm.module.sass'

interface AuthFormProps {
    className?: string
}

export const RegisterForm: FC<AuthFormProps> = ({ className }) => {
    const dispatch = useAppDispatch()
    const { errorText } = useAppSelector((state) => state.userSlice)
    const navigate = useNavigate()

    const {
        register,
        handleSubmit,
        formState: { errors },
        reset,
    } = useForm<TRegisterSchema>({
        resolver: zodResolver(registerSchema),
    })

    const onSubmit = async (data: TRegisterSchema) => {
        dispatch(registerUser(data))
        reset()
    }

    return (
        <div className={clsx(className, styles.wrapper)}>
            <form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
                <FormInput
                    title='Email'
                    register={register}
                    registerName='email'
                    errorText={errors.email?.message}
                    isErorr={!!errors.email}
                />
                <FormInput
                    title='Name'
                    register={register}
                    registerName='name'
                    errorText={errors.name?.message}
                    isErorr={!!errors.name}
                />
                <FormInput
                    title='Password'
                    register={register}
                    registerName='password'
                    errorText={errors.password?.message}
                    isErorr={!!errors.password}
                />
                <Button type='submit' className={styles.button} size='xl'>
                    Register
                </Button>
            </form>
        </div>
    )
}

My slice with async thunk

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import UserService from '../../api/Services/UserService'
import { AxiosError } from 'axios'

interface IInitialState {
    user: {
        accessToken: string
        email: string
        name: string
    }
    isError: boolean
    errorText: string
    isLoading: boolean
}

interface IAuthInfo {
    name: string
    email: string
    password: string
}

export const registerUser = createAsyncThunk(
    'user/register',
    async (data: IAuthInfo, { rejectWithValue }) => {
        try {
            const response = await UserService.register(
                data.email,
                data.password,
                data.name
            )

            return response
        } catch (error: AxiosError | unknown) {
            if (error instanceof AxiosError) {
                return rejectWithValue(error.response?.data)
            }
        }
    }
)

const initialState: IInitialState = {
    user: {
        accessToken: '',
        email: '',
        name: '',
    },
    errorText: '',
    isError: false,
    isLoading: false,
}

export const userSlice = createSlice({
    name: 'general',
    initialState,
    reducers: {},
    extraReducers: (builder) => {
        builder.addCase(registerUser.pending, (state) => {
            state.isLoading = true
            state.errorText = ''
            state.isError = false
        })
        builder.addCase(registerUser.fulfilled, (state, action) => {
            if (action.payload) {
                state.user.accessToken = action.payload?.accessToken
                state.user.email = action.payload.user?.email
                state.user.name = action.payload.user?.name

                state.errorText = ''
                state.isError = false
                state.isLoading = false

                localStorage.setItem('token', action.payload?.accessToken)
            }
        })
        builder.addCase(registerUser.rejected, (state, action) => {
            state.errorText = action.payload as string
            state.isError = true
            state.isLoading = false
        })
    },
})

export default userSlice.reducer

I tried to pull the isError state from redux and check it, that is, if isError = false, then redirect to another page, but the problem is that isError is false by default and this check loses its significance.

I'll repeat the question again. How do I make the redirect happen ONLY upon successful registration?

I will be glad of any advice on the topic and without. Thank you all!


Solution

  • registerUser is an asynchronous action, it returns a Promise that can be awaited in the calling code before calling any additional logic. Surround the action logic in a try/catch and unwrap the fulfilled/rejected result.

    Example:

    const onSubmit = async (data: TRegisterSchema) => {
      try {
        await dispatch(registerUser(data)).unwrap();
        // Redirect upon successful registration
        navigate(/* target path */, { replace: true });
      } catch(error) {
        // Registration failure
        // handle or ignore error/rejection
      } finally {
        reset();
      }
    }
    

    For details see: Handling Thunk Results