Command Pattern in React with Redux: Enhancing flexibility and State management

renu ganvir
4 min readJul 23, 2023

--

In the gigantic world of software development, design patterns play a crucial role in creating robust and maintainable applications. There are several design patterns, however, we will only talk about the behavioral pattern, which largely focuses on interaction and communication between objects and classes, providing solutions for organizing and managing how objects collaborate to achieve desired behaviors.

When considering React as a frontend library and redux as a state management tool, we know that it allows us to use and create the components in whatever way we see fit. However, as our application grows in complexity, managing state changes and handling various user interactions can become challenging.

This is where the Command pattern steps in. By introducing a structured approach to handling actions and state modifications, the command pattern enhances the maintainability, testability, and extensibility of our application. It also promotes the separation of concern and enables us to encapsulate behavior within reusable command objects, providing an elegant solution for orchestrating complex interactions.

In this article, we will delve into the world of the command pattern and explore its practical implementation in React with redux. We will examine the benefits of adopting this pattern and discuss real-world use cases where it shines.

I am so excited to dive into the power of command patterns and together we will unlock new possibilities for building scalable and maintainable applications. (I am considering you are aware how redux works.)

Command pattern in React with Redux

The command pattern is a behavioral design pattern in which the request is made to the commandInvoker, who then passes the request to the encapsulated command object. The Command object then transmits the request to the relevant method of the Receiver to conduct the desired action.

CommandInvoker: As the name implies, it is a mixture of Command and Invoker. It is also a combination of two functionalities. We can think of Command as an abstract class with an execute method that is responsible for carrying out Command-specific operations. And Invoker only calls the execute() method when it is required.

Concrete Command: Concrete Command implements the commandInvoker and has an execute function logic. As a result, each Concrete Command represents a specific request.

Receiver: Receiver is the actual component that performs the action requested by Command. It knows how to execute the command and carries out the requested action.

Implementation of Command Pattern

We will take a simple example of a TODO list, where we will implement add and delete functionalities, in this article we will only focus on the Command pattern.

Please check the entire code in my codesandbox.

The Command Executor is responsible for executing the command objects. It keeps a list of commands and executes them as necessary. The Command Executor is a Redux utility that interacts with the Redux dispatch function.

class CommandExecutor {
constructor(dispatch) {
this.queue = [];
this.dispatch = dispatch;
}

execute(command) {
this.queue.push(command);
command.execute(this.dispatch.bind(this));
}
}

export default CommandExecutor;

The Command Objects encapsulate actions and their parameters as objects, enabling delayed or queued execution of the actions. In the context of Redux, the Command Objects wrap action creators and the data needed for the actions. In our case we have encapsulated addTodo action and executed an async API call.

import axios from "axios";
import { addTodo, deleteTodo } from "./actions";
const apiUrl = "https://jsonplaceholder.typicode.com/todos";

class AddTodoCommand {
constructor(todo) {
this.todo = todo;
}

execute(dispatch) {
axios
.post(apiUrl, this.todo)
.then((response) => {
dispatch(addTodo({ id: this.todo.id, title: response.title }));
})
.catch((error) => {
console.error("Error creating todo:", error);
});
}
}

class DeleteTodoCommand {
constructor(id) {
this.id = id;
}

execute(dispatch) {
axios
.delete(`${apiUrl}/${this.id}`)
.then(() => {
dispatch(deleteTodo(this.id));
})
.catch((error) => {
console.error("Error deleting todo:", error);
});
}
}

export { AddTodoCommand, DeleteTodoCommand };

Let’s now understand the flow step by step.

  1. The Command Executor is created and set up with the Redux dispatch function.
  2. Command Objects encapsulate action creators and their parameters.
  3. When an action needs to be dispatched, a Command Object is created and passed to the Command Executor for execution.
  4. The Command Executor adds the Command Object to its list of commands and calls the execute method of the Command Object, passing the dispatch function as an argument.
  5. The Command Object calls the action creator with its parameters, dispatching the action to the Redux store.
  6. The action is processed by the reducer, updating the state in the Redux store.
  7. Components connected to the Redux store receive the updated state and re-render as needed.

Conclusion

In this article, we have explored the power and versatility of command pattern in the context of React with Redux. With behavioral design pattern we’ve unlocked the new possibilities for handling complex interaction and state management.
Remember, mastering design patterns and architectural concepts takes practice and experience. Keep exploring, experimenting, and learning from the react community to level-up your skills as a frontend developer.

Happy coding and may your React application thrive with the power of the command pattern!

--

--

Responses (1)