DEV Community

Afroze Kabeer Khan. M
Afroze Kabeer Khan. M

Posted on • Edited on

React + Redux = React + Context

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
};
Enter fullscreen mode Exit fullscreen mode

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 });
};
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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. 👩‍💻👨‍💻.... 😀

Top comments (8)

Collapse
 
josemunoz profile image
José Muñoz

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))
Collapse
 
droidmakk profile image
Afroze Kabeer Khan. M

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

Collapse
 
g1itcher profile image
G1itcher

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.

Collapse
 
droidmakk profile image
Afroze Kabeer Khan. M

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.

Collapse
 
seanmclem profile image
Seanmclem

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

Collapse
 
droidmakk profile image
Afroze Kabeer Khan. M

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.

Collapse
 
jimmymcbride profile image
Jimmy McBride

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.

Collapse
 
droidmakk profile image
Afroze Kabeer Khan. M

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.