Making Redux Developer Friendly with Rematch

Image for post
Image for post
Photo by Kerin Gedge on Unsplash

What’s the problem with using Redux? When I google Redux, it seems to be pretty obvious:

Image for post
Image for post

That’s what I found as well. For any who first uses Redux, the idea is quite simple, but there’s a steep learning curve to get around with concepts like “reducer”, “store”.

So not surpassingly there’re quite a few alternatives or enhancers coming to the market. And I’d like to introduce you to rematch.

But before we go into rematch, let’s just quickly recap how redux works:

Image for post
Image for post

At the core of Redux are reducers. A reducer is a function that return the next state based on action and current state. Then you have action that is basically payloads of information that send data from your application to your store. How action and reducer relates to each other? Well, reducers specify how the application’s state changes in response to actions sent to the store. And what is the store? It just stores the whole state of the app in an immutable object tree, like a physical store.

And you also have have listeners that can subscribe to the changes in states at the store (like mapStateToProps in React-Redux) , and middleware that will do some tricks before the actions hit the reducer and after it’s dispatched.

But the convenience of managing state comes with a cost. Except for the steep learning curve that is mentioned earlier, we also have quite a few times when we need to write repetitive codes — boilerplate so is called.

For example, whenever we have an async call to load data, we typically need 3 actions to show state before the action call, when the call is successful and when it fails. But luckily, we have rematch!

Rematch

Rematch is designed to help programmers write code that utilises Redux best-practices without action types, action creators, switch statements or thunks.

Just to be clear, Rematch is not an alternative to Redux, it’s a wrapper on top of Redux .

Instead of having reducers, async actions in separate places, in rematch you have it in model :

A Model is an object that contains:

Image for post
Image for post

In this way you have a

Maybe it’s easier to start with an example. Let’s say we have a cake lists which you can add more cake kind into the category or pull the whole list. So you will have a model like :

//src/model/cakes.jsimport api from '../api/cakeTrackerApi'

const cakes = {
state: {
cakeList: [],
cakeInput: '',
},
reducers: {
setCakeList(state, cakeList){
const sortedCakeList = cakeList.slice().sort(
(a,b) => a.name.localeCompare(b.name)
)
return {
...state,
cakeList: sortedCakesList,
};
},
setCakeInput(state, text){
return {
...state,
cakeInput: text,
}
},
},
effects: {
async loadCakeList(){
const cakeList = await api.getCakes()
this.setCakeList(cakeList)
},
onChange(text){
this.setCakeInput(text)
}

}

export default cakes

And in the view you have a correspondent Cake component:

//src/components/Cake.jsimport React from 'react'
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import {connect} from 'react-redux'
import { withStyles } from '@material-ui/styles';

const styles = theme => {
return {
root: {
color: 'red',
'& div': {
padding: '0px !important',
},
},
}
}

function Cake(props){
React.useEffect(() => {
props.cakeDispatch.loadCakeList()
}, [])

return(
<Autocomplete
options={props.cakeState.cakeList}
getOptionLabel={(option) => option.name}
style={{ width: 300 }}
onChange={(a,b,c) => {
props.cakeDispatch.onChange((b && b.name) || '')
}}
onInputChange={(a,b,c) => {
props.cakeDispatch.onChange(b || '')
}}
className={props.classes.root}
freeSolo={true}
renderInput={(params) =>
<TextField
{...params}
placeholder='e.g. Cheesecake'
variant="outlined"
/>}
/>
)
}
export default withStyles(styles)(connect(
state => ({
cakeState: state.cakes
}),
dispatch => ({
cakeDispatch: dispatch.cakes
})
)(Cake));

So what we see here.

Initial state:

const cakes = {
state: {
cakeList: [],
cakeInput: '',
},

This stay the same with redux original style.

Effects

{   effects: {     
logState(payload, rootState) {
console.log(rootState)
}
}

It seems to be a lot, but the biggest difference is the async/await keyword instead of using Thunks. So instead of letting thunks middleware to check if the action dispatched is a function and pass in access to some store methods: dispatch and getState, we simplify letting effects to be a async function..

{
effects: {
async loadData(payload, rootState) {
// wait for data to load
const response = await fetch('http://example.com/data')
const data = await response.json()
// pass the result to a local reducer
this.update(data)
}
},
reducers: {
update(prev, data) {
return {...prev, ...data}
}
}
}

So in our example props.cakeDispatch.loadCakeList() is an async function that will call the api to get all the cakes and then set the cake list to the sorted cake list from the api. And props.cakeDispatch.onChange() will simply set the state cakeInput to the text value.

Reducer

{
reducers: {
add: (state, payload) => state + payload,
}
}

The biggest difference between rematch and redux original style reducer — have you found it?

We don’t have type anymore! Neither do we need string constant like

const SET_CAKE ='SET_CAKE'

Now the action creator and reducer has been combined into one.

Lastly, don’t forget to init the store like below:

import { Provider } from ‘react-redux’
import App from ‘./App’
import { init } from ‘@rematch/core’
import * as models from ‘./models’
import ‘./index.css’
const store = init({ models });ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById(‘root’)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store