loading...

React + Redux = React + Context

droidmakk profile image Kabeer ( Afroze ) Khan Updated on ・3 min read

I know, many articles are already talking about whether or not to replace Redux with Context. If yes, what are the tradeoffs, etc? But I don't think this is one of them.

This is simply for fun 😁. But still, you can try this in your pet projects 😉.

Hoping you have installed NodeJS, npm, and npx

First, let's set up a basic react template. Go to your favorite directory you'd like to play with. Run,
npx create-react-app fooapp

Change the app directory cd fooapp.

Now start the app, npm start. I hope the app has started and is opened in your browser at http://localhost:3000/.

Create a folder store under src.
cd src && mkdir store

Create two files under store. index.js and handlers.js

In index.js file under store. We'll create a Context.

/** index.js **/
import React from "react";
import PropTypes from "prop-types";

// Import all handlers
import * as handlers from "./handlers";

// Default state
const initialState = { todos:[] };

// Export the context
export const Context = React.createContext({ state: initialState, handlers });

// Export the provider
export const Provider = ({ children }) => {
  // This will be our global state
  const [state, setState] = React.useState(initialState);

  // Modify our hanlders with state and setState
  // Thanks Jose for this one 👍
   const modHandlers = Object.keys(handlers).map(key => handlers[key](state, setState))

  // Feed the state and modified handlers to the provider
  return (
    <Context.Provider value={{ state, handlers: modHanlders }}>
      {children}
    </Context.Provider>
  );
};

Provider.propTypes = {
  children: PropTypes.children.isRequired
};

Let's create handlers to add / remove todo's from the list. In store/handlers.js.

/* handlers.js*/
export const addTodo = (state, setState) => todo => {
  state.todos.push(todo);
  setState({ ...state });
}

export const removeTodo = (state, setState) => i => {
  delete state.todos[i];
  setState({ ...state });
};

Update src/index.js file. Adding the following contents.

// src/index.js
import React from "react";
import ReactDOM from "react-dom";

import Todo from "./components/Todo";
import { Provider } from "./store";

function App() {
  return (
    <Provider>
      <div className="App">
        <h2 className="apptitle">Todo List</h2>
        <Todo />
      </div>
    </Provider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Don't mind if it throws an error. We will update it.

Create a folder componentsunder src directory. Add the three files to it Todo.js, TodoField.js & TodoItem.js.

In your components/Todo.js file create a component. That holds the todo list.

// components/Todo.js
import React from "react";

import TodoItem from "./TodoItem";
import TodoField from "./TodoField";
import { Context } from "../store";

const Todo = props => {
  // Get the state from Context using useContext hook
  const { state } = React.useContext(Context);

  return (
    <div>
      <TodoField />
      <ul>
        {state.todos.map((todo, i) => (
          <TodoItem value={todo} index={i} />
        ))}
      </ul>
    </div>
  );
};

export default Todo;

The error should've been gone by now.

In your components/TodoField.js add the following code.

// components/TodoField.js
import React from "react";

import { Context } from "../store";

const TodoField = () => {
  const [todo, setTodo] = React.useState(null);

  // Import the handlers from context
  const { handlers } = React.useContext(Context);

  // Handles form and other things
  const handleSubmit = e => {
    e.preventDefault();
    // Add the todo to the store
    handlers.addTodo(todo);
  };

  const handleChange = e => {
    setTodo(e.target.value);
  };

  // Form with field and submit button
  return (
    <form onSubmit={handleSubmit} onChange={handleChange}>
      <input type="text" value={todo} required />
      <input type="submit" value="Add Todo" />
    </form>
  );
};

export default TodoField;

In your components/TodoItem.js add the following code.

// components/TodoItem.js
import React from "react";

import { Context } from "../store";

const TodoItem = ({ value, index }) => {
  const { handlers } = React.useContext(Context);
  const removeFromTodo = e => {
    handlers.removeTodo(index);
  };

  return (
    <li>
      {value} <button onClick={removeFromTodo}>x</button>
    </li>
  );
};

export default TodoItem;

After adding all the files. Your app should be working like this.
Todo List App

All the data is manipulated from the single store and manipulated using handlers.

You could use get and set method in Lodash for complex JSON object operations.
PS: The above method can also handle async operations based on Promise.

The implementation is in codesandbox.
Happy Coding. 👩‍💻👨‍💻.... 😀

Posted on by:

droidmakk profile

Kabeer ( Afroze ) Khan

@droidmakk

Full Stack 👨‍💻. Developing Cross-platform applications, Microservices, PWA's & Cloud-native applications. JS/TS 💛.

Discussion

markdown guide
 

I have a bit of an issue with this 3 lines:

const handlerarr = Object.keys(handlers);
  const modHanlders = {};
  handlerarr.map((h, i) => (modHanlders[h] = handlers[h](state, setState)));

It could be easily reduced to a single line without mutations:

  const modHandlers = Object.keys(handlers).map(key => handlers[key](state, setState))
 

Could be done, Thanks for that. 😍😍
Changing it right away!

 

I've used a lot of flux in different UI frameworks, and it never feels right to me. No matter how I use it or what library implemented it, it always feels like it's solving a problem that didn't really exist. Services and observables, or context works perfectly fine, with significantly less code.

 

I think the scalability is the key player here. When you're application scales up maintaining the state in any app becomes difficult hence you put forth and state accessible at certain level of depth.

 

Your to-do-field component uses its own local usestate todo and setTodo? Instead of from the context?

 

Yes, That's the implementation. I wanted that to be local. As you can differentiate between what needs to be global and what needs to local.

 

Context is a really great tool. However, I still prefer Redux for bigger applications. Especially when dealing with API's that are preforming CRUD operations on multiple different end points. Redux has middle-ware that make it a far superior when you have many things going on. The logger middle-ware, or if you want to use the Redux tool kit instead, making debugging a much easier task when you really get in the thick of it.

 

Sure, I'd say not to that. I even like the event-driven architecture to manipulate the state. But still, I insist you try this out.