Chiamaka Umeh A frontend developer with a passion for designing highly-responsive user interfaces for JavaScript-based web and mobile apps using React and React Native.

Level up your state management with Rematch

5 min read 1584

React Rematch Level Up State Management

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!

Table of contents

What is Rematch?

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.

Notable Rematch 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.



Getting started with Rematch

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.

Models

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:

  • What is my initial state? state
  • How do I change the state? reducers
  • How do I handle async actions? 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.

Initialize a Redux store with 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.


More great articles from LogRocket:


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;

Dispatch action

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.

Conclusion

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.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Chiamaka Umeh A frontend developer with a passion for designing highly-responsive user interfaces for JavaScript-based web and mobile apps using React and React Native.

Leave a Reply