React tutorial working with redux toolkit with saga in typescript
In this article i will explain you step by step to use redux toolkit with saga in react typescript application
Step 1: Create react typescript application with following command
npx create-react-app my-app --template typescript
Step 2: Install redux, redux toolkit, redux saga, react-redux, redux-actions
In your newly created application install redux toolkit, redux saga and react-redux with following command
npm install --save react-redux redux-saga redux-actions @reduxjs/toolkit @types/react-redux @types/redux-saga @types/redux-actions
Install redux logger this helps in to log all triggered actions in the developer console
npm install --save-dev redux-logger @types/redux-logger
Install axios this helps in sending http request
npm install --save axios @types/axios
Step 3: Create Store
Store act as central repository for all your state management
Go to your project folder and create /src/store/index.ts this is the main file for your store creation where we will be initializing and creating store
import { configureStore } from "@reduxjs/toolkit";
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import rootReducer from "./rootReducer";
import { rootSaga } from "./rootSaga";
export default function configureMainStore() {
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: rootReducer,
middleware: [sagaMiddleware, logger]
devTools: process.env.NODE_ENV !== "production",
});
sagaMiddleware.run(rootSaga);
return { store };
}
Step 4: Create Root Reducer
Reducers are the pure functions that take the current state and action and return the new state and tell the store how to do
Create a main reducer file as /src/store/rootReducer.ts where we will combine all the sub reducers and export it from here. Copy paste following content inside your rootReducer
import { combineReducers } from "@reduxjs/toolkit";
import { userslice } from "./user";
import { appslice } from "./app";
const reducers = combineReducers({
user: userslice.reducer,
app: appslice.reducer,
});
Step 5: Create Auth Reducer
We will be creating auth reducer as sub reducer i'm creating an example of login and signup to show you all how we can work for authentication in redux saga
Create a file /src/store/userslice.ts and copy paste the following code
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { UserType } from "../types/user";
const initialState: UserType = {
userDetails: null,
token: null,
};
export const userslice = createSlice({
name: "user",
initialState,
reducers: {
setAuthenticationToken: (
state = initialState,
{ payload }: PayloadAction<any>
) => {
return {
...state,
token: payload,
};
},
loginAction: (state = initialState, { payload }: PayloadAction<any>) => {
return {
...state,
userDetails: payload,
};
},
},
extraReducers: {},
});
Step 7: Add types for Auth
Create a file /src/auth/store/types/user.ts and copy paste following code
export type UserType = {
userDetails: any,
token: any,
};
Step 7: Create Auth Action types
Actions are plain JavaScript object that must have a type attribute to indicate the type of action performed. It tells us what had happened. Types should be defined as string constants in your application as given below
Create a file /src/store/actions/user.actions.types.ts and copy paste following code inside that file
import { createAction } from "redux-actions";
export const LOGIN = "LOGIN";
export const login = createAction(LOGIN);
export const REGISTER = "REGISTER";
export const register = createAction(REGISTER);
Step 8: Create Saga middleware for Auth
Redux Saga is a middleware library used to allow a Redux store to interact with resources outside of itself asynchronously
Create file /src/stores/sagas/user.ts copy paste the following code
import { all, put, takeLatest } from "redux-saga/effects";
import { errorMessage, successMessage } from "../../utilities/notification";
import { userslice } from "../slices/user";
import { LOGIN, REGISTER } from "../actions/user.actions.types";
import axios from "axios";
import * as Effects from "redux-saga/effects";
const call: any = Effects.call;
const login = async (payload: { email: string, password: string }) => {
const { data } = await axios.post(
"https://reqres.in/api/login",
{ email: payload.email, password: payload.password },
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
return data;
};
const signup = async (payload: { email: string, password: string }) => {
const { data } = await axios.post(
"https://reqres.in/api/register",
{ ...payload },
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
return data;
};
function* registerHandler({ payload: { data, callback } }: any) {
try {
const response: { token: string } = yield call(signup, {
...payload,
});
if (callback) {
callback({ success: true, data: response.token });
}
} catch (error) {
if (callback) {
callback({ success: false, data: null });
}
}
}
function* loginSaga({ payload: { data, callback } }: any) {
try {
const response: { token: string } = yield call(signup, {
...payload,
});
yield put(userslice.actions.setAuthenticationToken(response.token));
if (callback) {
callback({ success: true, data: response.token });
}
} catch (error) {
if (callback) {
callback({ success: false, data: null });
}
}
}
function* authSaga() {
yield all([takeLatest(LOGIN, loginSaga)]);
yield all([takeLatest(REGISTER, registerHandler)]);
}
export default authSaga;
Step 9: Create Root saga which combine all the saga and export
Create a file /src/store/sagas/index.ts and copy paste the following code
import { all } from "redux-saga/effects";
import user from "./user";
const sagas = function* sagas() {
yield all([user()]);
};
export default sagas;
Step 10:Add Store provider to your index.ts file
Inside your src/index.tsx file copy paste the following code
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import configureStore from "./stores";
const { store } = configureStore();
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Step 11:Inside your login page use redux saga
import React, { useRef } from "react";
import { login } from "../store/actions/user.actions.types.ts";
import { connect } from "react-redux";
const Login: React.FC<{}> = (props: any) => {
const emailRef = useRef();
const passwordRef = useRef();
const callback = (data: any) => {
console.log("Inside callback after login");
};
const login = () => {
let data: any = {
values: {
email: emailRef.current.value,
password: passwordRef.current.value,
},
callback,
};
props.login(data);
};
return (
<div>
<div className="form-floating">
<input
type="email"
className="form-control"
name="email"
id="floatingInput"
placeholder="name@example.com"
ref={emailRef}
/>
<label htmlFor="floatingInput">Email address</label>
</div>
<div className="form-floating mt-3">
<input
type="password"
className="form-control"
name="password"
id="floatingPassword"
placeholder="Password"
ref={passwordRef}
/>
<label htmlFor="floatingPassword">Password</label>
</div>
<div className="checkbox mb-3 mt-3">
<label>
<input name="remember" type="checkbox" defaultValue="remember-me" />{" "}
Remember me
</label>
</div>
<button
onClick={() => {
login();
}}
className="w-100 btn btn-lg btn-warning"
>
Sign in
</button>
</div>
);
};
const mapDispatchToProps = (dispatch: any) => ({
login: (params: any) => dispatch(login(params)),
});
export default connect(null, mapDispatchToProps)(Login);