Two Techies Blogs

DevBlog - A Blog Template Made For Developers

Welcome to my blog. Subscribe and get my latest blog post in your inbox.

Effortless State Management: Integrating Firebase Realtime Database with Redux-Toolkit and Saga Middleware

Published 5 months ago

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.