State management, which determines how data is created, updated, and passed around within an application, is a critical aspect of frontend development. In an ever-changing world of technology, there is no shortage of libraries for accomplishing state management, such that it becomes confusing and overwhelming to decide which one to use.
In this article, I’ll introduce you to Rematch, a state management library that I’ve been using for a while now. If you already like using Redux, then you’ll definitely love Rematch. Built on Redux, Rematch is everything Redux is without the many confusing configurations and boilerplates.
This article is for frontend developers who are looking to explore new state management libraries and those already using Redux. We’ll explore how Rematch works, elaborate on its features, then build a simple to-do application to demonstrate how to use Rematch. Let’s get started!
Rematch is state management library built on Redux. According to the official docs, Rematch is Redux best practices without the boilerplate.
Redux is an amazing state management tool with excellent development tools, but it’s quite a hassle to set it up for use in a project. You have to add a lot of configurations, even installing some other helper libraries like Redux Thunk for it to work.
Rematch uses the same Redux concept but offers an easier way of setting up a central store and managing state in apps, meaning there is no configuration with Redux Thunk and no need for other Redux boilerplates like switch statements and action creators. Instead, Rematch provides these out of the box, which is definitely good news for frontend developers. Rematch also has a persistor to persist store data.
Before we learn how to use Rematch, let’s go through some of its cool features.
Rematch was built on Redux, allowing for easy migration, code interoperability, use of the Redux development tools, and more. However, unlike plain Redux, Rematch handles Thunks, switch statements, action types, and action creators out of the box with just one file. Therefore, you don’t need to add any complex configurations, reducing the overall size of your boilerplate.
Rematch includes built in side effects, supporting the native JavaScript async/await to call external API’s. Rematch exposes an API interface for creating custom plugins that extend its functionalities.
Written in TypeScript, Rematch easily supports TypeScript and has autocomplete for all your methods, state, and reducers. Lastly, Rematch is framework agnostic. It works great with React, but also Vue, Angular, and more.
To understand how Rematch works, let’s build a to-do app that uses Rematch to manage state. First, let’s install a new React app template:
npx create-react-app my-todo
When the app template is done installing, cd
into my-todo
. Let’s go ahead to install Rematch:
npm install @rematch/core
We’ll also use some functionalities from React Redux, so let’s install that:
npm install react-redux
Finally, start the development server with npm start
.
To use Rematch, we first have to define models. Models bring together state, reducers, and async actions in one place, showing a snapshot of your Redux store and how it changes. Models are objects that play a central role in state management.
These questions help you understand what needs to be in the model:
state
reducers
effects with async/await
Let’s create the model for our to-do app:
export const myTodos = { state: { 1: { todo: 'Learn React' }, }, reducers: { addTodo(state, todo) { return { ...state, [Date.now()]: { todo } } }, removeTodo(state, id) { delete state[id] return { ...state } } }, effects: { async asyncRemoveTodo(id) { await new Promise(resolve => setTimeout(() => resolve(), 1000)) this.removeTodo(id) } } }
We created a state
object that contains the initial state, which is a todo
task. We also created methods in the reducer
for updating state. Finally, we set up a promise to mimic API calls inside effects
that resolves after one second. effects
is for running side effects like API calls. Next, we need to initialize the store.
init()
By just calling the init()
method, you build a fully configured Redux store. It accepts a config
object with properties like models and plugins, returning a fully-configured Redux store with additional Rematch functionality.
The store that is created has all the capabilities of a Redux store, including dispatch()
and subscribe()
methods. In addition, Rematch offers custom features like the addModel()
method for lazy loading new models.
Let’s initialize the store inside the index.js
file:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { Provider } from "react-redux"; import { init } from "@rematch/core"; import * as models from "./model"; const store = init({ models }); ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') );
Notice that we’re using the Provider
component from React Redux, which is the official Redux UI binding library for React. The Provider
component makes the store available to the nested components that need to access it. We also imported the models and initialized the store with it.
Now, we’ll create two new components, one for displaying to-do items and the other for creating to-do items. Create a folder inside src
called components
and create two files in it, Todo.js
and AddTodo.js
.
In the Todo.js
file, we’ll access the state to get and display to-do items:
// Todo.js import React from 'react'; import { useSelector } from 'react-redux' function Todo() { const todosArray = useSelector((state) => { let todosIds = Object.keys(state.myTodos) return todosIds.map(id => ({ ...state.myTodos[id], id, })) }) return ( <ul> { todosArray?.map(val => { return ( <div key={val.id}> <li>{val.todo}</li> </div> ) }) } </ul> ) } export default Todo;
In the code above, we import the useSelector
Hook from React Redux to access the store right from our component. We fetched the state, which is an object, and converted it into an array so that we can easily loop through it and display it.
Let’s go ahead and create a form for adding to-do items in the AddTodo.js
component:
// AddTodo.js import React, { useState } from 'react' function AddTodo() { const [myTodo, setMyTodo] = useState('') const handleChange = (event) => { setMyTodo(event.target.value) } return ( <div> <form> <input type='text' placeholder='Enter todo' value={myTodo} onChange={handleChange} /> <button type="submit">Add Todo</button> </form> </div> ) } export default AddTodo;
You can trigger reducers and effects in your models using dispatch, just like plain Redux. You can call dispatch directly or with the dispatch\[model][action\](payload)
shorthand, allowing you to standardize your actions without writing action types or action creators.
Let’s import the useDispatch
Hook and dispatch an action to create a todo
when a user submits the form:
// AddTodo.js import { useDispatch } from 'react-redux' const dispatch = useDispatch() const handleSubmit = event => { event.preventDefault(); dispatch.myTodos.addTodo(myTodo) setMyTodo('') } // call the `handleSubmit` on form submit <form onSubmit={handleSubmit}> <input type='text' placeholder='Enter todo' value={myTodo} onChange={handleChange} /> <button type="submit">Add Todo</button> </form>
The full code for AddTodo.js
is seen below:
// AddTodo.js import React, { useState } from 'react' import { useDispatch } from 'react-redux' function AddTodo() { const [myTodo, setMyTodo] = useState('') const dispatch = useDispatch() const handleChange = (event) => { setMyTodo(event.target.value) } const handleSubmit = event => { event.preventDefault(); dispatch.myTodos.addTodo(myTodo) setMyTodo('') } return ( <div> <form onSubmit={handleSubmit}> <input type='text' placeholder='Enter todo' value={myTodo} onChange={handleChange} /> <button type="submit">Add Todo</button> </form> </div> ) } export default AddTodo;
It really is that simple. Let’s go back to the Todo.js
to implement the delete
and async
delete functionality. As usual, we’ll import the useDispatch
Hook to dispatch the delete
actions.
Below is the entire code for Todo.js
:
// Todo.js import React from 'react'; import { useSelector, useDispatch } from 'react-redux' function Todo() { const todosArray = useSelector((state) => { let todosIds = Object.keys(state.myTodos) return todosIds.map(id => ({ ...state.myTodos[id], id, })) }) const dispatch = useDispatch() const handleDelete = (id) => { dispatch.myTodos.removeTodo(id) } const handleAsyncDelete = (id) => { dispatch.myTodos.asyncRemoveTodo(id) } return ( <ul> { todosArray?.map(val => { return ( <div key={val.id}> <li>{val.todo}</li> <button onClick={() => handleDelete(val.id)}>Delete</button> <button onClick={() => handleAsyncDelete(val.id)}>Async Delete</button> </div> ) }) } </ul> ) } export default Todo;
We can go ahead to import the two components into App.js
and save them to view in the browser.
Many frontend developers have avoided learning Redux because of its complexity and cumbersomeness. However, Rematch does an excellent job of abstracting away most of that work, making it easy and fast to set up a Redux store.
Thanks to the minimalistic and approachable API of Rematch, you can develop beautiful and robust applications and take advantage of Redux’s established features. I hope you enjoyed this article! Be sure to leave a comment if you have any questions.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.