React Redux Toolkit is a powerful tool for managing state in React applications. It provides a simpler and more opinionated approach to Redux, reducing the amount of boilerplate code required to set up and maintain a Redux store. In this blog post, we’ll explore how to use React Redux Toolkit with Redux Saga middleware and integrate Firebase Realtime Database for real-time data synchronization.
What is Redux Saga?
Redux Saga is a middleware library that allows developers to handle asynchronous actions in Redux applications. It uses ES6 generator functions to simplify the code for complex asynchronous logic, making it easier to manage and test. With Redux Saga, you can write side effects as independent tasks that can be paused, resumed, or canceled at any time.
What is Firebase Realtime Database?
Firebase Realtime Database is a cloud-hosted NoSQL database that lets developers store and sync data in real-time. It stores data as JSON and updates it in milliseconds across all connected clients. Firebase Realtime Database is a good choice for applications that require real-time updates, such as chat, multiplayer games, or collaborative tools.
Setting up the project
To get started with React Redux Toolkit, Redux Saga, and Firebase Realtime Database, you will need to set up your project environment.
First, install the necessary dependencies in your project:
npm install @reduxjs/toolkit react-redux firebase redux-saga react-bootstrap
Next, create a Firebase project and obtain your Firebase configuration. You will need to initialize Firebase in your application. Create "firebase-config.js" file and add code .
import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";
const firebaseConfig = {
apiKey: "XXXXXXXXXXXXXX",
authDomain: "XXXXXXXXXXXXXX",
databaseURL: "XXXXXXXXXXXXXX",
projectId: "XXXXXXXXXXXXXX",
storageBucket: "XXXXXXXXXXXXXX",
messagingSenderId: "XXXXXXXXXXXXXX",
appId: "XXXXXXXXXXXXXX"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const db = getDatabase(app);
Configuring middleware
After setting up Firebase, configure Redux Saga middleware for your project. Create "saga.js" file and add code .
import { put,takeEvery } from "redux-saga/effects";
import { ADD_STUDENT } from "../constant/constant";
import StudentActions from "../firebaseDB/students_operations/student.actions";
import { errorInOperation, addStudent } from "../features/features_all/student/studentSlice";
function* addStudents(data) {
try {
const response = yield StudentActions.add_new_student(data);
if(response == true){
yield put(addStudent(data));
}
else{
yield put(errorInOperation({
error: true,
msg: "all fields are required"
}));
}
} catch (error) {
yield put(errorInOperation({
error: true,
msg: "failed to add student2"
}));
}
}
// Generator function
export function* watcherStudent() {
yield takeEvery(ADD_STUDENT, addStudents);
}
For Fork the watcherStudent saga under the file rootSaga.js.
import { all, fork } from "redux-saga/effects";
import { watcherStudent } from "./saga";
const rootSaga = function* () {
yield all([
fork(watcherStudent),
// Other forks
]);
};
export default rootSaga;
For Firebase Operation Create a separate class "StudentActions" in file "student.actions.js".
import { db } from "../config/firebase-config";
import {ref, set} from "firebase/database";
class StudentActions {
add_new_student = async (newStudent) => {
try {
await set(ref(db, 'students/' + newStudent.data.id), {
id: newStudent.data.id,
student_name: newStudent.data.student_name,
student_email: newStudent.data.student_email,
student_age: newStudent.data.student_age,
student_phone: newStudent.data.student_phone
})
return true;
} catch (error) {
return false;
}
}
}
export default new StudentActions()
Setting up the store
First Create a Reducer slices under the file studentSlice.js.
import { createSlice, nanoid } from "@reduxjs/toolkit";
const initialState = {
students:"",
messages: {
error: false,
msg: ""
}
}
export const studentSlice = createSlice({
name: "student",
initialState,
reducers: {
storeStudents: (state, action) => {
let data = action.payload.data;
state.students = data
},
addStudent: (state, action) => {
// set state after new stutdent added in firebase real time database
const student = {
id: action.payload.data.id,
student_name: action.payload.data.student_name,
student_email: action.payload.data.student_email,
student_age: action.payload.data.student_age,
student_phone: action.payload.data.student_phone
}
},
errorInOperation: (state, action) => {
const message = {
error: action.payload.error,
msg: action.payload.msg
}
state.messages = message
}
}
})
export const { addStudent, errorInOperation, storeStudents } = studentSlice.actions
export default studentSlice.reducer
Create Redux store using the configureStore method from React Redux Toolkit.
import { configureStore } from "@reduxjs/toolkit";
import studentSlice from "./features_all/student/studentSlice";
import createSagaMiddleware from 'redux-saga';
import rootSaga from "../saga/rootSaga";
const sagaMiddleware = createSagaMiddleware();
export const store = configureStore({
reducer: studentSlice,
// middleware: [sagaMiddleware],
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(sagaMiddleware)
})
sagaMiddleware.run(rootSaga)
Dispatching actions
Now that the store and middleware are set up, you can dispatch actions to fetch and Add data To Firebase. Create a component AddStudent.jsx .
/*
--------The source code is owned by the Two Techies.
*/
import React, { useEffect, useState } from 'react';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
import { useDispatch, useSelector } from 'react-redux';
import Students from '../Students';
import { ADD_STUDENT } from '../../constant/constant';
import { nanoid } from '@reduxjs/toolkit';
import ToastDefault, { notify } from '../ToastDefault';
import logoImage from "../../assets/images/2-techies-logo.png";
function AddStudent() {
const [student, setStudent] = useState({
id: nanoid(),
student_name: "",
student_email: "",
student_age: "",
student_phone: ""
});
const response = useSelector(state => state.messages);
// This
useEffect(() => {
if(response.error == true){
notify(response.msg, 'error');
}
},[response]);
const dispatch = useDispatch();
let name, value;
function getRecord(event) {
name = event.target.name;
value = event.target.value;
let student_record = { ...student, [name]: value };
setStudent(student_record);
}
const addStudentHandler = (e) => {
e.preventDefault();
if (student.student_name == '' || student.student_email == '' || student.student_age == '' || student.student_phone == '') {
notify('all fields are required', 'error');
} else {
dispatch({ type: ADD_STUDENT, data: student });
setStudent({
id: "",
student_name: "",
student_email: "",
student_age: "",
student_phone: ""
});
}
}
return (
<div className="container p-5">
<ToastDefault />
<h1 className='mb-5 row justify-content-sm-start justify-content-center text-sm-start text-center'>
<a className='text-decoration-none col-sm-4 col-12 d-flex justify-content-center justify-content-sm-start mb-3 mb-sm-0' href="https://two-techies.com/" target='_blank'><img src={logoImage} alt="Two-Techies" style={{width: '10rem', float: 'left'}}/></a>
<span className="col-sm-8 col-12">Redux Toolkit with saga</span> </h1>
<div className="card" style={{ width: 'auto' }}>
<div className="card-body">
<h3 className="card-title">Add Students</h3>
<div className="row">
<div className="col-md-6 col-12">
<div className="card" style={{ width: 'auto' }}>
<div className="card-body">
<Form onSubmit={addStudentHandler}>
<Form.Group className="mb-3" name="id" controlId="formBasicName">
<Form.Label>Name</Form.Label>
<Form.Control type="text" placeholder="Enter name" name='student_name' value={student.student_name} onChange={(e) => getRecord(e)} />
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" name='student_email' value={student.student_email} onChange={(e) => getRecord(e)} />
</Form.Group>
<Row className="mb-3 row">
<Form.Group className="mb-3 col-sm-6" controlId="formBasicAge">
<Form.Label>Age</Form.Label>
<Form.Control type="number" placeholder="Enter age" name='student_age' value={student.student_age} onChange={(e) => getRecord(e)} />
</Form.Group>
<Form.Group className="mb-3 col-sm-6" controlId="formBasicPhone">
<Form.Label>Phone</Form.Label>
<Form.Control type="tel" placeholder="Enter phone" name='student_phone' value={student.student_phone} onChange={(e) => getRecord(e)} />
</Form.Group>
</Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
</div>
</div>
<div className="col-md-6 col-12 py-md-0 py-5">
<div className="card" style={{ width: 'auto' }}>
<div className="card-body">
<Students />
</div>
</div>
</div>
</div>
</div>
<div className="card-footer text-center">
<figure>
<blockquote className="blockquote">
<p>Hire <a className='text-decoration-none' href="https://two-techies.com/" target='_blank'>Two Techies</a></p>
</blockquote>
<figcaption className="blockquote-footer">
Get Contact <cite title="Source Title"><a className='text-decoration-none' href="https://www.linkedin.com/in/sunnysahijwani" target='_blank'>@sunnysahijwani</a></cite>
</figcaption>
</figure>
</div>
</div>
</div>
)
}
export default AddStudent
also create a component with file name "Students.jsx" .
import React from 'react'
import { useSelector } from 'react-redux';
import Table from 'react-bootstrap/Table';
function Students() {
const students = useSelector(state => state.students)
return (
<>
<h3>Students list</h3>
<Table striped bordered hover responsive>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<th>Age</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
{students !=null && Object.keys(students).length > 0 && Object.values(students).map((key, value) => {
return <tr key={value + 1}>
<td>{value + 1}</td>
<td>{key.student_name}</td>
<td>{key.student_email}</td>
<td>{key.student_age}</td>
<td>{key.student_phone}</td>
</tr>
})
}
</tbody>
</Table>
</>
)
}
export default Students
After creating components, In "App.jsx" add following code.
import './App.css';
import AddStudent from './components/sub-components/AddStudent';
import { storeStudents } from './features/features_all/student/studentSlice';
import { db } from './firebaseDB/config/firebase-config';
import { onValue, ref } from 'firebase/database';
import { useEffect } from 'react';
import { store } from './features/store';
function App() {
// This useEffect is for fetching data from firebase realtime database
useEffect(() => {
const query = ref(db, 'students');
onValue(query, (snapshot) => {
const data = snapshot.val();
if(snapshot.exists()){
store.dispatch(storeStudents({data:data}))
}
})
},[]);
return (
<>
<AddStudent/>
</>
);
}
export default App;
For Bind Your application with store in file "index.jsx" add like :
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import {store} from './features/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
After setting everything its look like this :
Conclusion
In this blog post, we have explored how to use Redux Toolkit with Redux Saga middleware and integrate Firebase Realtime Database for real-time data synchronization. By using these tools together, developers can create scalable and robust applications with a clear separation of concerns between the UI and the data layer.
Redux Saga simplifies the code for complex asynchronous logic, allowing developers to write side effects as independent tasks that can be paused, resumed, or cancelled at any time. Firebase Realtime Database is a cloud-hosted NoSQL database that lets developers store and sync data in real-time. Together, these tools provide an efficient and intuitive approach to building modern applications with real-time data synchronization.
Preflight CORS Requests: The Gatekeepers of Cross-Origin Requests
Published 5 months ago