DEV Community

sakethk
sakethk

Posted on • Updated on

Create own react-redux using context api in react

Hello 👋,

In this article we are going to build own react-redux with the help of context api

Why we need redux in react ?

In react we need to share data between components. It will be very difficult with react state with the help of redux we can make it simple.

Here is an example.

const Root = () => {
  const [label, setLabel] = useState()
  return <div>
   <p>{label}</p>
   <Parent setLabel={setLabel}/>
  </div>
};

const Parent = props => {
  return <Child {...props} />;
};

const Child = props => {
  return <Subchild {...props} />;
};

const Subchild = ({ children, setLabel }) => {
  return <div>
   <button onClick={() => setLabel('Hello')}>Set Label</button>
   <p>{children}</p>
  </div>
};
Enter fullscreen mode Exit fullscreen mode

In the above example app has multiple levels Root -> Parent -> Child -> Subchild we are displaying label in Root level and we are setting label in Subchild level. For this we need to pass setLabel callback till Subchild level from root unnecessarily. Parent and Child has setLabel function but those components are not doing anything with that. Here it is small example so it is okay think how can we manage these things in large real time app 🤔

Solution

REDUX

image.png

How redux helps?

Redux will maintain a store (global state) independently. We can access and mutate the redux data directly from the component. For the above problem we will link Root and Subchild components with redux so those two components will have an access for global store so Root component can access the label at the same time Subchild component can set the label no need to pass anything through Parent and Child

Let's jump into development part 🚀

First we need to create context for global state

const {
  createContext,
} = require("react");

const context = createContext();

const { Provider, Consumer } = context;
Enter fullscreen mode Exit fullscreen mode

We created context successfully now lets create combineReducers along with dummy reducers for now

const reducer1 = (state, action) => {
  switch (action.type) {
    case "INSERT_X":
      return { ...state, x: action.data };
    case "DELETE_X":
      return { ...state, x: null };
    default:
      return { ...state };
  }
};

const reducer2 = (state, action) => {
  switch (action.type) {
    case "INSERT_Y":
      return { ...state, y: action.data };
    case "DELETE_Y":
      return { ...state, y: null };
    default:
      return { ...state };
  }
};

// zip is util function
const zip = (list1, list2) => {
  var obj = {};
  for (let i = 0; i < list1.length; i++) {
    obj[list1[i]] = list2[i];
  }
  return obj;
};

const combineReducers = (reducers) => {
  return (state, action) => {
    const _reducers = Object.keys(reducers);
    const _state = Object.keys(reducers).map((reducer) => {
      return reducers[reducer](state[reducer], action);
    });

    return zip(_reducers, _state);
  };
};
Enter fullscreen mode Exit fullscreen mode

Next we need to create Provider method to init store in App and connect method to consume it on component

const StoreProvider = ({ children }) => {
  const rootReducer = combineReducers({ reducer1, reducer2 });

  const [state, dispatch] = useReducer(rootReducer, {});

  return <Provider value={{ state, dispatch }}>{children}</Provider>;
};


 const connect = (mapStateTopProps, mapDispatchToProps) => {
  return (Component) => (props) => {
    return (
      <Consumer>
        {({ state, dispatch }) => {
          const dispatchProps = mapDispatchToProps(dispatch);
          const stateProps = mapStateTopProps(state);
          return <Component {...props} {...stateProps} {...dispatchProps} />;
        }}
      </Consumer>
    );
  };
};
Enter fullscreen mode Exit fullscreen mode

Hook approach to mutate and access the state

const useSelector = (fn) => {
  const { state } = useContext(context);
  return fn(state);
};

const useDispatch = (fn) => {
  const { dispatch } = useContext(context);

  return dispatch;
};
Enter fullscreen mode Exit fullscreen mode

Finally the code will be like this

const {
  useContext,
  createContext,
  useReducer,
  useState,
  useEffect
} = require("react");

const context = createContext();

const { Provider, Consumer } = context;

const reducer1 = (state, action) => {
  switch (action.type) {
    case "INSERT_X":
      return { ...state, x: action.data };
    case "DELETE_X":
      return { ...state, x: null };
    default:
      return { ...state };
  }
};

const reducer2 = (state, action) => {
  switch (action.type) {
    case "INSERT_Y":
      return { ...state, y: action.data };
    case "DELETE_Y":
      return { ...state, y: null };
    default:
      return { ...state };
  }
};

const zip = (list1, list2) => {
  var obj = {};
  for (let i = 0; i < list1.length; i++) {
    obj[list1[i]] = list2[i];
  }
  return obj;
};

const combineReducers = (reducers) => {
  return (state, action) => {
    const _reducers = Object.keys(reducers);
    const _state = Object.keys(reducers).map((reducer) => {
      return reducers[reducer](state[reducer], action);
    });

    return zip(_reducers, _state);
  };
};

const Store = ({ children }) => {
  const rootReducer = combineReducers({ reducer1, reducer2 });

  const [state, dispatch] = useReducer(rootReducer, {});

  return <Provider value={{ state, dispatch }}>{children}</Provider>;
};

export const connect = (mapStateTopProps, mapDispatchToProps) => {
  return (Component) => (props) => {
    return (
      <Consumer>
        {({ state, dispatch }) => {
          const dispatchProps = mapDispatchToProps(dispatch);
          const stateProps = mapStateTopProps(state);
          return <Component {...props} {...stateProps} {...dispatchProps} />;
        }}
      </Consumer>
    );
  };
};

export const useSelector = (fn) => {
  const { state } = useContext(context);
  return fn(state);
};

export const useDispatch = (fn) => {
  const { dispatch } = useContext(context);

  return dispatch;
};

export default Store;
Enter fullscreen mode Exit fullscreen mode

We are done with redux part 👏🏻

To use this in your app wrap your root component with StoreProvider and use connect in the components where you want to consume the state

Here is sandbox link with example

Thank you!!!!

🚨🚨⚠️⚠️ : Don’t use this code in production. This is just for educational purposes.

You can now extend your support by buying me a Coffee.

Buy Me A Coffee

Top comments (5)

Collapse
 
phryneas profile image
Lenz Weber

This is a cool project to grasp how some of the internals of react-redux work, but please make sure to never do it in production.
For one, this imitates a very old style of Redux and modern Redux would be only a fraction of that code (and a lot more readable).
Also, React-Redux manages subscriptions very differently internally. Context is not suited for this and will lead to performance issues once your state starts growing. For more info on that, see dev.to/javmurillo/react-context-al...

To learn modern Redux, please take a look at the official tutorial: redux.js.org/tutorials/essentials/...
Or for just a quick overview of what changed a few years ago: redux.js.org/tutorials/fundamental...

Collapse
 
sakethkowtha profile image
sakethk • Edited

Yes this is not for production just for educational purpose. The reason why I created is when I was learning react. I didn’t understand how redux handle state. Later with the help of context i was able to build on my own. So I thought my learning would help others to understand internal things
Cheers!

Collapse
 
phryneas profile image
Lenz Weber

Yup, it's perfectly fine for that :)

Thread Thread
 
ivan_jrmc profile image
Ivan Jeremic

With recoil I have everything even cleaner and more organized.

Collapse
 
karimrostov profile image
KarimRostov

Thanks.