kess
kess

Reputation: 1309

Redux Toolkit: How to call async function, then dispatch action within a reducer

I'm trying to learn redux toolkit

What I want to achieve: When the app is started run the check() function, retireve saved credentials via getCredentials, if they exist (!=null) log in with those credentials, if not show the login screen (the login screen in visible when isAuthenticated === false)

I get the following error:

Require cycle: state\reducers\authSlice.ts -> state\store.ts -> state\reducers\authSlice.ts

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.        
 ERROR  TypeError: undefined is not an object (evaluating '_authSlice.default.reducer')

If I comment out store.dispatch then the code runs without errors.

Here is the code:

store.tsx

import { configureStore } from "@reduxjs/toolkit";
import authSlice from "./reducers/authSlice";

export const store = configureStore({
    reducer: {
        auth: authSlice.reducer
    }
});

export async function getStore(){
    return store;
}

setImmediate(()=>{
    store.dispatch(authSlice.actions.check());
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

authSlice.tsx

import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit'
import { store } from '../store';
import { isAuthenticated, login } from '../../api/auth';
import { getCredentials, saveCredentials } from '../../storage/auth';
import { Credentials } from '../../interfaces/auth';

interface AuthState {
    isAuthenticated?: boolean
    username?: string
}

const initialState: AuthState = {
    isAuthenticated: undefined,
    username: undefined
}

const authSlice = createSlice({
    name: "auth",
    initialState,
    reducers: {
        check(state){
            if (state.isAuthenticated == undefined){
                getCredentials().then(auth=>{
                    if (auth){
                        login(auth).then(()=>{
                            store.dispatch(authSlice.actions.loginSuccess(auth))
                        })
                    } else {
                        store.dispatch(authSlice.actions.unauthenticated())
                    }
                })
            }
        },
        loginSuccess(state,action: PayloadAction<Credentials>){
            state.username = action.payload.username;
            state.isAuthenticated = true;
        },
        unauthenticated(state){
            state.isAuthenticated = false;
        },
    },
});

export default authSlice;

Some people say calling store.dispatch from a reducer is an antipattern, but how else an I supposed to achieve the same logic?

Upvotes: 1

Views: 1195

Answers (1)

phry
phry

Reputation: 44086

A reducer has to be pure. That means (among others) two things:

  • A reducer is not allowed to have side effects (no calling getCredentials or login in there and generally no async logic at all).
  • A reducer cannot dispatch an action

You are missing quite a few basics here, so I would suggest that maybe you follow the official Redux Essentials Tutorial and read up how you should do things like side effects. That kind of logic has no place in a reducer.

Upvotes: 1

Related Questions