Installation
Installation is as simple as running the npm command:
- npm
- Yarn
bashnpm install @rematch/core
bashnpm install @rematch/core
bashyarn add @rematch/core
bashyarn add @rematch/core
Basic usage​
Step 1: Define models​
Model brings together state, reducers, async actions in one place. It describes a slice of your redux store and how it changes.
Understanding configuration of models is as simple as answering a few questions:
- What is my initial state? state
- How do I change the state? reducers
- How do I handle async actions? effects with async/await
Below we define a simple model count.
- JavaScript
- TypeScript
jsexport const count = {state: 0, // initial statereducers: {// handle state changes with pure functionsincrement(state, payload) {return state + payload;},},effects: (dispatch) => ({// handle state changes with impure functions.// use async/await for async actionsasync incrementAsync(payload, rootState) {await new Promise((resolve) => setTimeout(resolve, 1000));dispatch.count.increment(payload);},}),};
jsexport const count = {state: 0, // initial statereducers: {// handle state changes with pure functionsincrement(state, payload) {return state + payload;},},effects: (dispatch) => ({// handle state changes with impure functions.// use async/await for async actionsasync incrementAsync(payload, rootState) {await new Promise((resolve) => setTimeout(resolve, 1000));dispatch.count.increment(payload);},}),};
- Use helper method
createModelto create a model. - You must pass the
RootModeltype that is exported on your index.ts of your models. - State is automatically infered, if your state contains complex types you only need to use an
asLook at count-react-ts example on questions.ts
All the examples of Rematch with TypeScript are fully tested in our testing suite, so feel free to look at the /examples folder for an easier integration with your codebase.
./models/count.tstsimport { createModel } from "@rematch/core";import { RootModel } from ".";export const count = createModel<RootModel>()({state: 0, // initial statereducers: {// handle state changes with pure functionsincrement(state, payload: number) {return state + payload;},},effects: (dispatch) => ({// handle state changes with impure functions.// use async/await for async actionsasync incrementAsync(payload: number, state) {console.log("This is current root state", state);await new Promise((resolve) => setTimeout(resolve, 1000));dispatch.count.increment(payload);},}),});
./models/count.tstsimport { createModel } from "@rematch/core";import { RootModel } from ".";export const count = createModel<RootModel>()({state: 0, // initial statereducers: {// handle state changes with pure functionsincrement(state, payload: number) {return state + payload;},},effects: (dispatch) => ({// handle state changes with impure functions.// use async/await for async actionsasync incrementAsync(payload: number, state) {console.log("This is current root state", state);await new Promise((resolve) => setTimeout(resolve, 1000));dispatch.count.increment(payload);},}),});
./models/index.tstsimport { Models } from "@rematch/core";import { count } from "./count";export interface RootModel extends Models<RootModel> {count: typeof count;}export const models: RootModel = { count };
./models/index.tstsimport { Models } from "@rematch/core";import { count } from "./count";export interface RootModel extends Models<RootModel> {count: typeof count;}export const models: RootModel = { count };
Example with a more complex state
tsimport { createModel } from "@rematch/core";import { RootModel } from "./models";type QuestionType = "true-false" | "other-value";type Question = {title: string;};interface CountState {questions: Array<Question>;questionType: QuestionType;}export const count = createModel<RootModel>()({state: {questions: [],questionType: "true-false",} as CountState, // typed complex statereducers: {// handle state changes with pure functionsincrement(state, payload: number) {return state;},},effects: (dispatch) => ({// handle state changes with impure functions.// use async/await for async actionsasync incrementAsync(payload: number, state) {console.log("This is current root state", state);await new Promise((resolve) => setTimeout(resolve, 1000));dispatch.count.increment(payload);},}),});
tsimport { createModel } from "@rematch/core";import { RootModel } from "./models";type QuestionType = "true-false" | "other-value";type Question = {title: string;};interface CountState {questions: Array<Question>;questionType: QuestionType;}export const count = createModel<RootModel>()({state: {questions: [],questionType: "true-false",} as CountState, // typed complex statereducers: {// handle state changes with pure functionsincrement(state, payload: number) {return state;},},effects: (dispatch) => ({// handle state changes with impure functions.// use async/await for async actionsasync incrementAsync(payload: number, state) {console.log("This is current root state", state);await new Promise((resolve) => setTimeout(resolve, 1000));dispatch.count.increment(payload);},}),});
Step 2: Init store​
init is the only method you need to call to build a fully configured Redux store. You can visit api reference to learn more about all configuration parameters that can be used but as a bare minimum it is enough to provide models object. It should contain mapping from a model name to model definition (created in step 1).
- JavaScript
- TypeScript
store.jsjsimport { init } from "@rematch/core";import * as models from "./models";const store = init({ models });export default store;
store.jsjsimport { init } from "@rematch/core";import * as models from "./models";const store = init({ models });export default store;
store.tstsimport { init, RematchDispatch, RematchRootState } from "@rematch/core";import { models, RootModel } from "./models";export const store = init({models,});export type Store = typeof store;export type Dispatch = RematchDispatch<RootModel>;export type RootState = RematchRootState<RootModel>;
store.tstsimport { init, RematchDispatch, RematchRootState } from "@rematch/core";import { models, RootModel } from "./models";export const store = init({models,});export type Store = typeof store;export type Dispatch = RematchDispatch<RootModel>;export type RootState = RematchRootState<RootModel>;
Step 3: Dispatch actions​
With dispatch you can trigger reducers & effects in your models. Dispatch standardizes your actions without the need for writing action types or action creators. Dispatch can be called directly, just like with plain Redux, or with the dispatch[model][action](payload) shorthand.
jsconst { dispatch } = store;// state = { count: 0 }// reducersdispatch({ type: "count/increment", payload: 1 }); // state = { count: 1 }dispatch.count.increment(1); // state = { count: 2 }// effectsdispatch({ type: "count/incrementAsync", payload: 1 }); // state = { count: 3 } after delaydispatch.count.incrementAsync(1); // state = { count: 4 } after delay
jsconst { dispatch } = store;// state = { count: 0 }// reducersdispatch({ type: "count/increment", payload: 1 }); // state = { count: 1 }dispatch.count.increment(1); // state = { count: 2 }// effectsdispatch({ type: "count/incrementAsync", payload: 1 }); // state = { count: 3 } after delaydispatch.count.incrementAsync(1); // state = { count: 4 } after delay
Step 4: View​
Rematch can be used with native redux integrations such as "react-redux". See an example below.
- JavaScript
- TypeScript
App.jsjsimport React from "react";import ReactDOM from "react-dom";import { Provider, connect } from "react-redux";import store from "./store";const Count = (props) => (<div>The count is {props.count}<button onClick={props.increment}>increment</button><button onClick={props.incrementAsync}>incrementAsync</button></div>);const mapState = (state) => ({count: state.count,});const mapDispatch = (dispatch) => ({increment: () => dispatch.count.increment(1),incrementAsync: () => dispatch.count.incrementAsync(1),});const CountContainer = connect(mapState, mapDispatch)(Count);ReactDOM.render(<Provider store={store}><CountContainer /></Provider>,document.getElementById("root"));
App.jsjsimport React from "react";import ReactDOM from "react-dom";import { Provider, connect } from "react-redux";import store from "./store";const Count = (props) => (<div>The count is {props.count}<button onClick={props.increment}>increment</button><button onClick={props.incrementAsync}>incrementAsync</button></div>);const mapState = (state) => ({count: state.count,});const mapDispatch = (dispatch) => ({increment: () => dispatch.count.increment(1),incrementAsync: () => dispatch.count.incrementAsync(1),});const CountContainer = connect(mapState, mapDispatch)(Count);ReactDOM.render(<Provider store={store}><CountContainer /></Provider>,document.getElementById("root"));
App.tsxtsimport * as React from "react";import { Provider, connect } from "react-redux";import { RootState, Dispatch } from "./store";const mapState = (state: RootState) => ({count: state.count,});const mapDispatch = (dispatch: Dispatch) => ({increment: () => dispatch.count.increment(1),incrementAsync: () => dispatch.count.incrementAsync(1),});type StateProps = ReturnType<typeof mapState>;type DispatchProps = ReturnType<typeof mapDispatch>;type Props = StateProps & DispatchProps;class Count extends React.Component<Props> {render() {return (<div>The count is {props.count}<button onClick={() => props.increment()}>increment</button><button onClick={() => props.incrementAsync()}>incrementAsync</button></div>);}}const CountContainer = connect(mapState, mapDispatch)(Count);ReactDOM.render(<Provider store={store}><CountContainer /></Provider>,document.getElementById("root"));
App.tsxtsimport * as React from "react";import { Provider, connect } from "react-redux";import { RootState, Dispatch } from "./store";const mapState = (state: RootState) => ({count: state.count,});const mapDispatch = (dispatch: Dispatch) => ({increment: () => dispatch.count.increment(1),incrementAsync: () => dispatch.count.incrementAsync(1),});type StateProps = ReturnType<typeof mapState>;type DispatchProps = ReturnType<typeof mapDispatch>;type Props = StateProps & DispatchProps;class Count extends React.Component<Props> {render() {return (<div>The count is {props.count}<button onClick={() => props.increment()}>increment</button><button onClick={() => props.incrementAsync()}>incrementAsync</button></div>);}}const CountContainer = connect(mapState, mapDispatch)(Count);ReactDOM.render(<Provider store={store}><CountContainer /></Provider>,document.getElementById("root"));