Skip to content
Advertisement

React Redux createAsyncThunk actions don’t update the state

I’m implementing a registration page to my nodejs api. With createAsyncThunk I implemented the function to fetch the user data and update the state. The api works correctly, in fact it returns the created user data in the payload when the action is dispatched. But the user state is not updated and stays the initial state.

Here the user slice code:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

const initialState = { 
    username: null,
    email: null,
    password: null,
    gamesPlayed: null,
    gamesWon: null,
    gamesLost: null,
    draws: null,
    loading: false,
    error: null    
 }

 const serializeAndStringify = obj => {
    const seen = []
    JSON.stringify(
        obj,
        (key, val) => {
            if(val != null && typeof val === 'object') {
                if(seen.indexOf(val) >= 0)
                   return
                seen.push(val)
            }
            return val                
        }
    )
    return seen[0]
}

export const fetchUserData = (requestUrl, userData) => 
    createAsyncThunk(
        'userAuth',
        async () => 
            axios.post(requestUrl, serializeAndStringify(userData))
    )

const userSlice = createSlice({
    name: 'user',
    initialState,
    reducers: {
        victory (state) {
            state.gamesPlayed++
            state.gamesWon++
        },
        defeat (state) {
            state.gamesPlayed++
            state.gamesLost++
        },
        draw (state) {
            state.gamesPlayed++
            state.draws++
        }         
    },
    extraReducers: {
        [fetchUserData.pending]: (state, action) => {
            state.loading = true
        },
        [fetchUserData.fullFilled]: (state, { payload }) => {
            state.loading = false
            state.username = payload.data.username
            state.email = payload.data.email
            state.password = payload.data.password             
        },
        [fetchUserData.rejected]: (state, action) => {
            state.loading = false
            state.error = action.payload
        }
    }
})

export default userSlice.reducer

The store.js:

import { configureStore } from '@reduxjs/toolkit'
import logger from 'redux-logger'
import thunk from 'redux-thunk'

import userReducer from './user'

const reducer = {
    user: userReducer
}

const middlewares = [thunk]

if(process.env.NODE_ENV === 'development')
    middlewares.push(logger)  

const store = configureStore({
    reducer,
    middleware: middlewares,
    devTools: process.env.NODE_ENV !== 'production',
})

export default store

The registration page:

import { Fragment } from 'react'
import { useNavigate } from 'react-router'
import { useDispatch } from 'react-redux'

import { fetchUserData } from '../../redux/user.js'

import './register.styles.scss'

const RegisterPage = () => {
    const navigate = useNavigate()
    const dispatch = useDispatch()

    const handleSubmit = event => {
        event.preventDefault()
        const { username, email, password } = event.target
        let userData
        let requestUrl = '/auth/'

        if(username) {
            userData = { 
                username: username.value, 
                email: email.value, 
                password: password.value 
            }
            requestUrl += 'registration'
        } else {
            userData = {  
                email: email.value,
                password: password.value
            }         
            requestUrl += 'login'   
        }
        dispatch(fetchUserData(requestUrl, userData)())
    }  

    return (
        <Fragment>
        <div className='register-page'>            
            <form className='container' onSubmit={handleSubmit}>
                <span>WELCOME BACK</span>
                <input name='email' required type='email' placeholder='Email' />
                <input name='password' required type='password' minLength='8' placeholder='Password' />
                <div className='button-wrapper'>
                    <button type='submit'>LOG IN</button>
                    <button className='google-button'>
                        <a href='http://localhost:4000/auth/google'>
                            LOG IN WITH GOOGLE
                        </a>
                    </button>
                </div>
            </form>
            <form className='container' onSubmit={handleSubmit}>
                <span>REGISTER</span>
                <input name='username' required type='text'  placeholder='Username' />
                <input name='email' required type='email' placeholder='Email' />
                <input name='password' required type='password' minLength='8' placeholder='Password' />
                <div className='button-wrapper'>
                    <button type='submit'>SIGN IN</button>
                    <button className='google-button'>
                        <a href='http://localhost:4000/auth/google'>
                            SIGN IN WITH GOOGLE
                        </a>
                    </button>
                </div>
            </form>   
        </div>
        <button onClick={() => navigate('/', {replace: true})}>Go back</button>       
        </Fragment>
    )
}

export default RegisterPage

here an image of the console

Advertisement

Answer

I modified the request field in this way:

export const fetchUserData = createAsyncThunk(
    'userAuth', 
    async ({ requestUrl, userData }) => {
        const resp = await axios.post(requestUrl, serializeAndStringify(userData))
        return resp.data
    }
)

And instead of writing fulfilled I had written fullFilled

Old code:

[fetchUserData.fullFilled]: (state, { payload }) => ...

New code:

[fetchUserData.fulfilled]: (state, { payload }) => ...

And now it works

Advertisement