DEV Community

Cover image for Build Your Own redux from scratch
Sai gowtham
Sai gowtham

Posted on

13 6

Build Your Own redux from scratch

Redux is a State Mangement library for React apps as it helps to manage the app state in a single Object it means the whole app state lives on a single Object.

If you try to connect a redux store you have to do some boilerplate setup to your react app often sometimes confusing.

So that's why we write it from scratch.

Create a store.js file in your react app.

first, we need to create a dispatch function, subscribe function, thunk function

import { reducer } from './reducers'; //import your reducer
let state;
const getState = () => state;
const listeners = [];
const dispatch = action => {
state = reducer(action, state);
listeners.forEach(listener => listener())
};
const subscribe = (listener) => {
listeners.push(listener)
return () => {
listeners.filter(lis => lis !== listener)
}
};
dispatch({});
const reducers = () => reducer;
reducers(); //getting the reducers
function Async(cb, request) {
request(cb);
}
//helps to do async things
const thunk = function(cb, request, delay) {
if (delay) {
return setTimeout(() => {
Async(cb, request);
}, delay);
}
Async(cb, request);
};
export { getState, dispatch, thunk,subscribe};
view raw store.js hosted with ❤ by GitHub

1.getState function helps to get the app current state.
2.thunk is used to do aysnc things you can even delay the network request.

create a reducers.js file

const initalState = {
num: 0,
todos: [],
data: "",
users:null
}
function reducer(action, state =initalState ) {
switch (action.type) {
case "INC":
return {
...state,
num: state.num + 1
};
case "DEC":
return {
...state,
num: state.num - 1
};
case "ADD_TODO":
return {
...state,
todos: [...state.todos, action.text]
};
case "GET_DATA":
return {
...state,
data: action.data
};
case 'GET_USERS':
return {
...state,
users:action.users
}
default:
return state;
}
}
export {reducer}
view raw reducers.js hosted with ❤ by GitHub

Reducer

when we dispatch an action it gives the new app state instead of mutating the old state.

How to connect our redux to the React app?

open your index.js file and import subscribe from the store that's it you are connected to the store like how i did in below code.

import React from "react";
import { render } from "react-dom";
import "./index.css";
import App from "./App";
import { subscribe } from './store';

subscribe(()=>render(
  <App />,
  document.getElementById("root")
))
Enter fullscreen mode Exit fullscreen mode

Now let's implement a counter and todo, and send some network requests so that we can know our redux is working correctly or not.

todo.js file

import React from 'react';
import { getState, dispatch} from './store'
class Todo extends React.Component {
OnAddHandler = () => {
if (this.input.value)
dispatch({
type: 'ADD_TODO',
text: this.input.value
})
this.input.value = ''
}
render() {
return (
<div>
<input placeholder="Some Text" ref={node => {
this.input = node
}}
/>
<button onClick={this.OnAddHandler} >Add Todo</button>
<ul>
{getState().todos.map((tod) =>
<li key={Math.random()}>{tod}</li>
)}
</ul>
</div>
)
}
}
export default Todo;
view raw todo.js hosted with ❤ by GitHub

in above code first, we imported getState and dispatch from the store.

when we click an add todo button we are dispatching the type of the action with a payload,getState helps to get the added todos from the store.

counterbuttons.js file

import React from "react";
import {dispatch} from './store'

function Inc() {
    dispatch({
        type: 'INC'
    })
}
function Dec() {
    dispatch({
        type: 'DEC'
    })
}

const width = { width: '2rem' ,fontSize:'1.2rem'}

const CounterButtons= () => (
  <div>
    <button onClick={Inc} style={width} >+</button>
    <button onClick={Dec}  style={width} >-</button>
  </div>
);

export default CounterButtons;
Enter fullscreen mode Exit fullscreen mode

It's time to send a network requests using thunks and thunks are used to make a network requests.

create a thunks.js file

import { dispatch, thunk } from "./store";
import axios from "axios";

export const users = () => thunk(
    function (res) {
        dispatch({
            type: "GET_USERS",
            users: res.data
        });
    }, (cb) => {
        axios.get('https://jsonplaceholder.typicode.com/users')
            .then(response => cb(response)) 
            .catch(err => cb({ err:'Error occurred'}))
    },5000 //delay time
)
Enter fullscreen mode Exit fullscreen mode

thunk function takes the three arguments first two are callback functions last
argument is delay and it is optional.

in the first callback function, you need to invoke the dispatch with the type of action and payload

in second callback you need to make a network request whenever response comes back wrap with cb(callback) function. so that you can take the response from the first call back function parameter.

FetchData Component

import React from "react";
import { getState } from "./store";
import { users } from "./thunks";

function Loading() {
  return <h1 style={{ color: "red" }}>Loading</h1>;
}

class FetchData extends React.Component {
  componentDidMount() {
    users();
  }
  Users = () => {
    if (getState().users) {
      return getState().users.map(user => (
        <ul key={user.id}>
          <li>{user.name}</li>
          <li>{user.email}</li>
        </ul>
      ));
    } else {
      return <h1 style={{color:'red'}}>Delaying request for 5seconds</h1>;
    }
  };

  render() {
    return (
      <div>
        <ul>
          <li>{getState().data ? getState().data : <Loading />}</li>
        </ul>

        <hr />
        <h1>Users</h1>
        <hr />

        {this.Users()}
      </div>
    );
  }
}

export default FetchData;
Enter fullscreen mode Exit fullscreen mode

that's it we are done with creating all components.

Now we need to import these components in App.js file because our app doesn't aware of these components.

App.js file

import React, { Component } from "react";
import CounterButtons from "./counterbuttons";
import Text from "./text";
import Todo from "./todo";
import FetchData from "./fetch";
import { getState} from "./store";
class App extends Component {
render() {
return (
<div className="App">
<h1>Counter</h1>
<h1>{getState().num}</h1>
<CounterButtons />
<h1>Todo</h1>
<Todo />
<h1>Fetch Data</h1>
<FetchData />
</div>
);
}
}
export default App;
view raw App.js hosted with ❤ by GitHub

Hoo successfully completed

final output

Hope you guys enjoyed...👍🏻

Code Repository

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (7)

Collapse
 
fpaghar profile image
Fatemeh Paghar

The structure you've outlined encourages modularity by organizing components, styles, and utilities into separate directories. This modularity not only improves maintainability but also allows for easier scalability as your project grows.

The separation of concerns is well-maintained in your project structure. Components are in the ui directory, styles in styles, and utilities in utils. This makes it clear where to find and update specific parts of the application.

Great tutorial! Your step-by-step explanation and code snippets make it easy to follow along and understand the core concepts of Redux. Building Redux from scratch helps demystify its inner workings. I especially appreciate how you've included practical examples like the Todo component, CounterButtons, and FetchData to demonstrate the integration with React components. Keep up the good work! 👏🚀

Collapse
 
ivanoung profile image
Ivan Oung

That's pretty cool, thanks!

Collapse
 
sait profile image
Sai gowtham

it's my pleasure Ivan

Collapse
 
aditya81070 profile image
Aditya Agarwal

Are you following Udacity React nanodegree. This code seems very very familiar to me: :)

Collapse
 
sait profile image
Sai gowtham

No ...

Collapse
 
aditya81070 profile image
Aditya Agarwal

then what tutorials you are following?

Collapse
 
chadsteele profile image
Chad Steele

Great article! Thanks!
I think you'll like this alt to redux as well.
dev.to/chadsteele/eventmanager-an-...

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more