My React Native Journey: From Overusing Redux to Smarter State Management 3
Before we learn Jest, I want to show you how Zustand simplify our code. When we use Redux in old way at least we need to create :
- Action Types
- Actions
- Reducer
- Thunk
As you can see in the diagram bellow a lot of boiler plate that we need to write.
Compared with Zustand we just need single file
Fortunately, now we have Redux-Toolkit although we still need Thunk for the Logic we can get rid of Action Types, Action, Reducer and replace it with Slice. So our code more simple than before.
From this 😒
export const LOGIN_REQUEST = "LOGIN_REQUEST";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAILURE = "LOGIN_FAILURE";
export const REGISTER_REQUEST = "REGISTER_REQUEST";
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAILURE = "REGISTER_FAILURE";
export const LOGOUT_REQUEST = "LOGOUT_REQUEST";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
export const LOGOUT_FAILURE = "LOGOUT_FAILURE";
export const AUTH_STATE_CHANGED = "AUTH_STATE_CHANGED";
import User from "@/src/types/user";
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAILURE,
REGISTER_REQUEST,
REGISTER_SUCCESS,
REGISTER_FAILURE,
LOGOUT_REQUEST,
LOGOUT_SUCCESS,
LOGOUT_FAILURE,
AUTH_STATE_CHANGED,
} from "../types/actionTypes";
export const loginRequest = () => ({
type: LOGIN_REQUEST,
});
export const loginSuccess = (user: User) => ({
type: LOGIN_SUCCESS,
payload: user,
});
export const loginFailure = (error: any) => ({
type: LOGIN_FAILURE,
payload: error,
});
export const registerRequest = () => ({
type: REGISTER_REQUEST,
});
export const registerSuccess = (user: User) => ({
type: REGISTER_SUCCESS,
payload: user,
});
export const registerFailure = (error: any) => ({
type: REGISTER_FAILURE,
payload: error,
});
export const logoutRequest = () => ({
type: LOGOUT_REQUEST,
});
export const logoutSuccess = () => ({
type: LOGOUT_SUCCESS,
});
export const logoutFailure = (error: any) => ({
type: LOGOUT_FAILURE,
payload: error,
});
export const authStateChanged = (user: User | null) => ({
type: AUTH_STATE_CHANGED,
payload: user,
});
import User from "@/src/types/user";
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAILURE,
REGISTER_REQUEST,
REGISTER_SUCCESS,
REGISTER_FAILURE,
LOGOUT_REQUEST,
LOGOUT_SUCCESS,
LOGOUT_FAILURE,
AUTH_STATE_CHANGED,
} from "../types/actionTypes";
type AuthState = {
user: User | null;
loading: boolean;
error: string | null;
};
const initialState: AuthState = {
user: null,
loading: false,
error: null,
};
const authReducer = (state = initialState, action: any): AuthState => {
switch (action.type) {
case LOGIN_REQUEST:
case REGISTER_REQUEST:
case LOGOUT_REQUEST:
return {
...state,
loading: true,
error: null,
};
case LOGIN_SUCCESS:
case REGISTER_SUCCESS:
return {
...state,
user: action.payload,
loading: false,
error: null,
};
case LOGOUT_SUCCESS:
return {
...state,
user: null,
loading: false,
error: null,
};
case LOGIN_FAILURE:
case REGISTER_FAILURE:
case LOGOUT_FAILURE:
return {
...state,
user: null,
loading: false,
error: action.payload,
};
case AUTH_STATE_CHANGED:
return {
...state,
user: action.payload,
loading: false,
error: null,
};
default:
return state;
}
};
export default authReducer;
To just this 🤩
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
// Define the user type
type User = {
displayName: string | null;
email: string | null;
phoneNumber: string | null;
photoURL: string | null;
};
// Define the initial state
interface AuthState {
user: User | null;
loading: boolean;
error: string | null;
}
const initialState: AuthState = {
user: null,
loading: false,
error: null,
};
// Create the slice
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
loginRequest(state) {
state.loading = true;
state.error = null;
},
loginSuccess(state, action: PayloadAction<User>) {
state.user = action.payload;
state.loading = false;
state.error = null;
},
loginFailure(state, action: PayloadAction<string>) {
state.user = null;
state.loading = false;
state.error = action.payload;
},
registerRequest(state) {
state.loading = true;
state.error = null;
},
registerSuccess(state, action: PayloadAction<User>) {
state.user = action.payload;
state.loading = false;
state.error = null;
},
registerFailure(state, action: PayloadAction<string>) {
state.user = null;
state.loading = false;
state.error = action.payload;
},
logoutRequest(state) {
state.loading = true;
},
logoutSuccess(state) {
state.user = null;
state.loading = false;
state.error = null;
},
logoutFailure(state, action: PayloadAction<string>) {
state.user = null;
state.loading = false;
state.error = action.payload;
},
authStateChanged(state, action: PayloadAction<User>) {
state.user = action.payload;
state.loading = false;
state.error = null;
},
},
});
// Export the generated action creators
export const {
loginRequest,
loginSuccess,
loginFailure,
registerRequest,
registerSuccess,
registerFailure,
logoutRequest,
logoutSuccess,
logoutFailure,
authStateChanged,
} = authSlice.actions;
// Export the reducer
export default authSlice.reducer;
I also try Saga to compare the implementation, with Saga we replace Thunk and add Root Saga to collect all Saga.
I decide to use Redux-Toolkit when try Saga so our code not grow to big with boiler plate 😂.
And also we can get rid of some import that needed for using useSelector when dispatching action using custom hooks like bellow :
import { useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "../redux-saga/authStore"; // Import your store types
import {
loginRequest,
registerRequest,
logoutRequest,
authStateChanged,
} from "../redux-toolkit/slices/saga/authSlice";
import User from "@/src/types/user";
import LoginInputs from "@/src/types/loginInput";
export const useAuth = () => {
const dispatch = useDispatch<AppDispatch>();
const { user, loading, error } = useSelector(
(state: RootState) => state.auth
);
const loginUser = (input: LoginInputs) => {
dispatch(loginRequest(input));
};
const registerUser = (input: LoginInputs) => {
dispatch(registerRequest(input));
};
const logoutUser = () => {
dispatch(logoutRequest());
};
const changedUser = (user: User) => {
dispatch(authStateChanged(user));
};
return {
user,
loading,
error,
loginUser,
registerUser,
logoutUser,
changedUser,
};
};
Source
You can check full code here, so you can read more detail code :