Chapter 8: The Rematch Plugins Ecosystem

In this chapter, we'll learn how Rematch plugins are built and which hooks are exposed for interacting with our store and models. We'll look at the most common use cases of each hook and how they work internally. Also, we'll focus on the official list of plugins provided by Rematch, analyzing their internal architecture and configuring them inside our Amazhop application.

In this chapter, we'll cover the following topics:

  • Introduction to Rematch plugins
  • The Rematch loading plugin
  • The Rematch select plugin
  • The Rematch Immer plugin
  • The Rematch updated plugin
  • The Rematch persist plugin

By the end of the chapter, you will understand how Rematch plugins extend the functionality of Rematch with minimal impact on your bundle size and performance, even improving performance in some cases. Also, you will learn how they work internally and how they can be used in a real application.

Technical requirements

To follow along with this chapter, you will need the following:

  • Basic knowledge of Vanilla JavaScript and ES6 features
  • Basic knowledge of HTML5 features
  • Node.js >= 12
  • Basic knowledge of React and CSS
  • A browser (Chrome or Firefox, for instance)
  • A code editor (Visual Studio Code, for instance)

You can find the code for this chapter in the book's GitHub repository: https://github.com/PacktPublishing/Redux-Made-Easy-with-Rematch/tree/main/packages/chapter-8.

To get started with this chapter, we'll use the application we implemented in Chapter 6, React with Rematch - The Best Couple - Part II, as a template to add our Rematch plugins. This chapter will give you an understanding of how Rematch plugins work and how to add them to any Rematch application.

Introduction to Rematch plugins

Probably the first question that we should ask is what is a plugin? A plugin is a software add-on that is installed on software, enhancing its capabilities. When we think about how plugins work, what comes to our minds are browser extensions where the browser has just minimal functionality, or, better put, just what's needed for most users.

Making software extendable using plugins is a breeze because our base software is simple and plugins can extend this base functionality, making the software even more powerful but covering all use cases. If a user only wants the base software, they can decide to not use plugins, and if another user has a complex use case, they can implement a plugin that covers that use case without jamming other users. Sometimes building moldable software is the best approach.

Rematch is one of the best examples of modularity, and offering plugins was one of the best decisions we took when building Rematch.

The @rematch/core package contains minimal code to make Rematch work with Redux, letting any developer introduce an easy state-management solution to their application with best practices in less than 2 KB.

Rematch plugins provide the ability to extend Rematch functionality by overwriting the configuration, adding new models, or even replacing the whole store with new properties. Every plugin can contain two static properties called exposed and config, and four hooks. These hooks are executed from the core package and let us enhance the current store.

To understand from a low-level perspective how Rematch plugins are built and which properties are exposed, we can analyze this diagram:

Figure 8.1 – Rematch plugin architecture

Every Rematch plugin can contain any of these properties, so let's explain them.

config

The config property is just a plain object with two properties, models and redux. They allow you to add additional models to the current store. When the store initializes, it will check whether any plugin contains a config schema and will merge our current init() configuration with any config from the plugins.

The shape of these properties is the same as the one accepted by the init() method. In the next section, The Rematch loading plugin, we'll see how these properties let us configure an extra model to handle the loading status of our asynchronous effects.

Here's an example of a custom config inside a plugin:

const plugin = {

    config: {

        redux: {

            combineReducers: customCombineReducers,

        }

    },

}

In this source code, we're adding a custom combineReducers function via the plugin, but we could change any value of the redux configuration.

exposed

The exposed property allows you to assign extra properties to the store for communicating between plugins. It's executed before the onModel and onStoreCreated hooks, allowing us to define custom methods of our plugin that could be called later through the store.

This property can return a plain object or a function that returns an object with custom accessors. If using a function, the Rematch store is passed through the first parameter. This is really useful if you need to recover a value from the current state.

This property is heavily used in the Rematch select plugin. We'll analyze it later, but to understand this concept a bit more, imagine that we want a debug method in our store that returns the store state and debugs it inside the browser console:

const plugin = {

  exposed: (rematchStore) => ({

    debug: () => console.debug(rematchStore.getState())

  }),

},

This plugin will add a property to the store that can be called later with store.debug() and will use console.debug on the current state.

createMiddleware

The createMiddleware property is used for creating a custom middleware that needs access to Rematch internals that are available in the Rematch bag, in the first parameter of this property.

For example, we could create a plugin whose main function is logging the current dispatched action and the next state result:

const plugin = {

  createMiddleware: (rematchBag) => (store) => (next) =>   (action) => {

    console.log('dispatching', action)

    const result = next(action)

    console.log('next state', store.getState())

    return result

  }

}

The main difference between Rematch createMiddleware and the original implementation of Redux middlewares is that Rematch createMiddleware is written as a series of four nested functions since a native Redux middleware is a series of three nested functions. These nested functions let us access the following:

  • rematchBag: Contains all the properties that Rematch accumulates in the initialization step, including models, Redux configuration, and so on.
  • store: Contains the two principal functions of Redux, getState() and dispatch().
  • next: Responsible for moving to the next middleware in the chain. If the middleware that is executed next is the last one, it executes the original store.dispatch function. Just think about it as the name indicates – executing this function passes to the next middleware in the pipeline.
  • action: This argument contains the action that is passed through the dispatch method.

If you don't need to use the rematchBag argument, you can just use the config.redux.middlewares property from the init() function; there's no need to create a plugin.

onReducer

The onReducer hook is executed when a base reducer is created for a model. The main objective of this lifecycle is overwriting the original reducer created by our models, returning a new instance of a new reducer will be enough to get an enhanced reducer.

The use case for this hook is to apply additional libraries to reducers; for instance, Rematch persist uses this hook to persist reducers in a persisted storage, for example, local storage:

onReducer(reducer, modelName) {

  const reducerConfig = nestedPersistConfig[modelName]

  if (reducerConfig) {

    return persistReducer(reducerConfig, reducer)

  }

  return undefined

},

The onReducer hook passes as the first argument the original reducer reference, the current modelName property – normally it's a string – as the second argument, and the Rematch bag as the third argument.

onRootReducer

The onRootReducer hook acts in the same way as the onReducer hook, but in this case, when a rootReducer object is created in the store, it can return a new rootReducer object, which will overwrite the one created by Rematch.

Let's take the Rematch persist example:

onRootReducer(rootReducer) {

  return persistReducer(persistConfig, rootReducer)

},

It can also persist in any storage that the rootReducer property defined in the init() method.

onModel

When all the setup for the models is completed, the reducers and dispatchers are created and the onModel hook is executed for each model. Normally, it is used for collecting information about models, reducers, and effects; every time our store adds a new model, this hook is executed. Also, it's useful to overwrite any model for a custom one or create new properties inside an existing model.

This hook is also executed every time a model is added dynamically to the store using the store.addModel() function. This function allows us to dynamically inject models into our store at any moment; this is a really interesting feature since our store could just contain the minimum models, to initialize faster, and we could then inject models dynamically when they're requested.

This hook is heavily used in the Rematch loading and Rematch updated plugins since they create new models based on other models.

To understand which arguments are returned inside the onModel property, we can check this code:

const plugin = {

  onModel: (model, rematchBag) => {}

}

This code snippet just shows which arguments are passed through the onModel hook:

  • model: This is the actual model reference. When we add a new model to the store using store.addModel() or we initialize the store for the first time, model notifies every plugin that handles the onModel listener of every model that the user passed to the models property inside the init() function. In this way, we can make changes to any model before the store is created.
  • rematchBag: Contains all the properties that Rematch accumulates in the initialization step, models, Redux configuration, and so on.

In Chapter 9, Composable Plugins - Create Your First Plugin, we'll create a plugin that uses the onModel hook and we'll go into more detail on this.

onStoreCreated

onStoreCreated is the last hook of any Rematch plugin lifecycle, running at the end when the Rematch store is ready. It can return a new store, in which case, it will overwrite the one created by Rematch.

Usually, onStoreCreated is used to add extra properties or functions to the store. For example, the Rematch persist plugin uses redux-persist under the hood. We persist the entire store because the redux-persist library requires us to do it:

onStoreCreated(store) {

persistor = persistStore(store, persistStoreConfig, callback)

},

These are all the lifecycles that Rematch exposes and they can be used to extend Rematch functionality without further problems, making Rematch Core maintainable, smaller, and more modular.

To load any Rematch plugin, we just need to import the desired plugin and add it to the plugins array of our init() function:

import loadingPlugin from '@rematch/loading'

import { init } from '@rematch/core'

import models from './models'

init({

models,

plugins: [loadingPlugin()],

})

If the plugin itself can be configured, we can pass this configuration through the loadingPlugin() initializer.

The Rematch team, in the development process of the library, asked its users which features they were interested in being added as plugins and worked hard to bring them to life. There is a collection of five official plugins, which are going to be explained later in this chapter and put into practice in our Amazhop application.

In the next section, we'll see how Rematch introduced an interesting list of official plugins that are heavily maintained and really recommended to add to any application. We'll start with the Rematch loading plugin, one of the most useful plugins that Rematch offers.

The Rematch loading plugin

The official loading plugin for Rematch adds automated loading indicators for effects. This plugin means that we don't have to worry about managing states such as loading: true and loading: false by ourselves.

Installing and configuration

To install this plugin, as it is published like the @rematch/core package, you can just use yarn:

yarn add @rematch/loading

This plugin adds just 596 bytes to our application.

The loading plugin accepts one optional argument, which is an object with the following properties:

  • name: Since this plugin will create a new model for handling all the states of loading our effects, we can overwrite the name of this model. By default, it will be called loading.
  • type: By default, this plugin keeps track of running effects using Booleans, loading: true and loading: false, but you can change that behavior and use numbers or even detailed statuses, like "error" or "success". If you use type: "number", the plugin will track the number of times an effect was executed, like a counter, and if you use type: "full", each effect will contain an object inside the loading plugin state with this shape:

    {

      "loading": true,

      "success": false,

      "error": false

    }

    This is super useful for building user interfaces where you should check if the Promise resolved an Error, or to see whether everything went fine.

  • whitelist: An array of effect names that you want to use the loading plugin for. By default, it's empty because the loading plugin works for all effects.
  • blacklist: An array of effect names that you don't want to use the loading plugin for – just the opposite of whitelist.

This plugin will create a schema based on three groups:

{

  "global": true,

  "models:": {

    "count": true

  },

  "effects": {

    "count": {

      "increment": true

    }

  }

}

We can use a global state that will be true when any effect in any one of our models is loading. This means that when any of our asynchronous effects in our whole store is not fulfilled, loading will be true.

This global state is split into three sections:

  • global: If any effect in our whole store is not fulfilled, this will be true.
  • models: Automatically creates an object with every model of our store, so we can know which models are still loading.
  • effects: Used to individually track which effect inside a model is still loading.

Basically, the plugin goes from a more general state (global) to the most detailed state (effects).

Example usage

Taking the application we've been developing in this book, just install the package with yarn as mentioned previously and use it directly in our src/store/index.js file:

import { init } from "@rematch/core";

import createLoadingPlugin from "@rematch/loading";

import { shop, cart } from "./models";

export const store = init({

  models: { shop, cart },

  redux: {

    rootReducers: {

      RESET: () => undefined,

    },

  },

  plugins: [createLoadingPlugin()],

});

After introducing these changes and starting the application with yarn dev as usual, you can see that inside our Redux DevTools extension, a new model called loading automatically appears that contains the schema previously mentioned:

Figure 8.2 – Redux DevTools extension with the @rematch/loading plugin

Imagine a use case where our users enter our shop and they have a poor network connection, such as 3G, meaning our getProducts() request takes longer than expected. Right now, a white screen will appear. Let's implement a little spinner that will be shown automatically using the @rematch/loading plugin.

To do that, modify our src/components/List.jsx component to show a spinner when the getProducts() effect is still loading:

export const List = () => {

  const isLoading = useSelector(

    (rootState) => rootState.loading.effects.shop.getProducts

  );

In this code snippet, we're just using the same strategy we used in other situations to recover data from our store. In this case, we're returning the status of the getProducts() effect.

Now, we can modify our infinite scroll hook and tell this hook when our application is still loading and doesn't need to request more data because the previous one is still pending:

  const [infiniteRef] = useInfiniteScroll({

    loading: isLoading,

We can use ternaries in our return function to render a Spinner component or return the products list instead. Let's modify our List component:

      <div

        role="list"

        className="grid grid-cols-2 xl:grid-cols-3 2xl:grid-        cols-4 3xl:grid-cols-5 gap-8 2xl:gap-5 3xl:gap-5"

      >

        {products.map((product) => (

          <Product key={product.id} product={product} />

        ))}

      </div>

      {(isLoading || (hasNextPage && !query)) && (

        <div className="mt-5" ref={infiniteRef}>

          <Spinner />

        </div>

      )}

We're using the isLoading property to indicate when to show the spinner. In our case, we already had this logic, but for the infiniteRef reference used to notify IntersectionObserver of when to pull more data, we have the OR operand (||). This logical operator will return true if one of the specified conditions is true. In our case, we'll show the spinner if our application is loading or has more data and the user has scrolled to the bottom.

We can see that when we scroll to the bottom, a spinner appears automatically. Or, if it's the first load, instead of showing just an empty screen, we correctly show the loading indicator:

Figure 8.3 – Amazhop showing a loading indicator on first load

In the same way, this feature could be implemented for the favorite button. Since it's an asynchronous effect that uses network resources, it could be delayed due to external factors such as poor connectivity, so it's good to add a spinner for when the user clicks on the favorite button.

In the next section, we'll analyze how the Rematch select plugin works, and how we can improve the performance of our application using selectors.

The Rematch select plugin

Selectors are a really good performance booster for our Rematch/Redux applications. A selector is simply a function that accepts the root state as an argument and returns data that is derived from that root state.

Reselect (https://github.com/reduxjs/reselect) is the official library for creating memoized and composable selector functions, and the Rematch select library is built on top of Reselect.

But why are selectors performance boosters? Because selectors aren't recomputed unless one of their arguments changes.

Installing and configuration

To install @rematch/select, you can use yarn and install it as follows:

yarn add @rematch/select

This package is just 596 bytes, so it has a minimal impact on our bundle size. None of the official Rematch plugins is more than 600 bytes in size.

This plugin accepts one optional argument, which is an object with the following properties:

  • sliceState: Sometimes our store is not a plain JavaScript object and can't be accessed using rootState.model.name. This property accepts a custom function for getting the model's state based on the store's root state and the model object.
  • selectorCreator: You can replace the internal Reselect library with a different one by providing a custom function for creating selectors that has the same interface as Reselect.

Usually, the Rematch select plugin won't require any changes in the configuration on your side; the default one should work for most applications out there.

Now, our Rematch model, which has accepted a name, state, reducers, and effects, now also accepts the selectors property:

selectors: (slice, createSelector) => ({ })

The selectors property must be a function that accepts the following arguments.

slice

slice is the first argument that the selectors property returns, and it works in two ways:

  • We can create a simple memoized selector based on the current model state:

    total () {

      return slice(cart =>

        cart.reduce((a, b) => a + (b.price * b.amount), 0)

      )

    },

    This function will only be recomputed when the cart state changes.

  • slice also works as a shortcut to access the current model state. Instead of writing (rootState) => rootState.currentModel, we can just use slice() and it will return the current state.

createSelector

The createSelector function is the default Reselect createSelector function. It takes one or more selectors, or an array of selectors, computes their values, and passes them as arguments to the function result.

createSelector determines whether the value returned by an input selector has changed between calls using reference equality, ===. By default, it has a cache size of 1, which means that the selector only stores the preceding value of each input selector.

All these options can be overwritten through the configuration of the plugin using another selectorCreator function; for example, Re-reselect, which is built on top of Reselect, tries to enhance selectors with deeper memoization and better cache management:

Figure 8.4 – Reselect and Re-reselect comparison

With these concepts clear, we're ready to introduce some selectors in our Amazhop application.

Example usage

Taking the application we've been developing in this book, just install @rematch/select with yarn and use it directly in our src/store/index.js file:

export const store = init({

  models: { shop, cart },

  redux: {

    rootReducers: {

      RESET: () => undefined,

    },

  },

  plugins: [createLoadingPlugin(), createSelectPlugin()],

});

Internally, the Rematch select plugin uses the exposed property. As you'll remember, this property allows us to expose some extra methods that weren't initially defined.

Looking at our source code, we can easily see that src/components/Cart/Cart.jsx needs some selectors, one for recomputing the total price of the cart and one for recovering the products. We start with this:

  const { addedIds, quantityById } = useSelector((rootState) =>   rootState.cart);

  const cartProducts = useSelector((rootState) =>

    addedIds.map((id) => getProduct(rootState, id))

  );

  const totalPrice = useSelector((rootState) =>

    addedIds.reduce(

      (total, id) =>

        total + getProduct(rootState, id).price *         getQuantity(rootState, id),

      0

    )

  );

But what about if we just implement all this business logic inside our models as selectors and then call them from our React views:

const quantityById = useSelector((rootState) => rootState.cart.quantityById);

const cartProducts = useSelector(store.select.cart.getCartProducts);

const totalPrice = useSelector(store.select.cart.total);

Apart from being cleaner in terms of maintainability, we're extracting logic that could be reused in another view just by using these lines.

Let's go to src/store/models/cart.js and introduce a new property called selectors that returns selectors:

selectors: (slice, createSelector) => ({

  total() {

    return createSelector(

      [slice, (rootState) => rootState.shop.products]

      (cartState, products) =>

        cartState.addedIds.reduce(

          (total, id) =>

            total +

            getProduct(products, id).price *             getQuantity(cartState, id),

          0

        )

    );

  },

})

What we did here was just move the logic of the code that we had in Cart.jsx to createSelector. The first argument of the createSelector function is an array with the data used to create the memoized selector, the slice function returns the current state, and the second arrow function returns the products of our shop state.

Then, the second argument of the createSelector function accesses these values, the first argument, cartState, is the slice function, and products is the arrow function. It is done like this to be able to memoize this selector, meaning that it won't be recomputed unless the products array changes.

With this selector introduced, we can use it from the view as we suggested previously:

const totalPrice = useSelector(store.select.cart.total);

Now, we can implement another selector to return the cart products:

getCartProducts() {

  return createSelector(

    [slice, (rootState) => rootState.shop.products],

    (cartState, products) =>

      cartState.addedIds.map((id) => getProduct(products, id))

  );

}

This selector expects the same data as the total() selector, but in this case returns products instead of a number.

Now, thanks to our testing suite, we can run yarn test and we can see that our tests passed correctly, meaning that we improved the performance of the application and we're sure that the functionality is still there.

In Chapter 12, Rematch Performance Improvements and Best Practices, we'll see how we can optimize these selectors even more, and we'll analyze in depth how to track things and whether our changes correctly reflect a performance boost in our user experience.

One of the most recommended libraries for Redux code bases is Immer.js. It's a tiny library of 3 KB that handles immutability with normal JavaScript objects, arrays, and so on, without the requirement of using object spreading, thereby making our code more concise. Rematch offers a plugin that enables Immer by just adding this plugin to our plugins property.

The Rematch Immer plugin

As you'll remember from when we spoke about immutability and how Redux and Rematch handle changes of state, Redux forced us to create a new copy of the state on every reducer; we couldn't just mutate the current state and return it because it caused incorrect renders and it's not the correct way of doing things.

Immer.js is a tiny package that allows us to work with immutable state in a more comfortable way because it's based on the copy-on-write mechanism.

The idea is that you will apply your changes to a temporary draft state that is a proxy of the current state. Once all our mutations are done, Immer will produce the next state based on the mutations to the draft state.

To be clearer, instead of spreading and cloning our state all the time, we can just make safe mutations to our state, resulting in an immutable state.

Installation and configuration

To install @rematch/immer, you can use yarn and install it as follows:

yarn add @rematch/immer immer

@rematch/immer has a bundle size of 143 bytes.

Like the other plugins, the Rematch Immer plugin accepts one optional argument, which is an object with the following properties:

  • whitelist: An array of model names to define which reducers should be wrapped with Immer.
  • blacklist: An array of model names to define which reducers shouldn't be wrapped with Immer.

    By default, reducers from all models will be wrapped with the Immer produce function.

Example usage

Taking the application we've been developing in this book, just install the package with yarn and use it directly in our src/store/index.js file:

export const store = init({

  models: { shop, cart },

  redux: {

    rootReducers: {

      RESET: () => undefined,

    },

  },

  plugins: [

   createLoadingPlugin(),

   createSelectPlugin(),

   createImmerPlugin(),

  ],

});

Now, if we run yarn test, we'll see some errors related to Immer warnings about not using everything that Immer offers, so let's refactor our model's logic to follow the Immer convention.

Taking the cart.js model, we can start refactoring the ADD_TO_CART reducer from this:

return {

  addedIds: [...state.addedIds, product.id],

  quantityById: {

    ...state.quantityById,

    [product.id]: 1,

  },

};

This is the code that ADD_TO_CART contains when returning the new state. Now, instead, we can just use mutation functions such as array.push(). Do you remember that we couldn't use .push because pushing to an array kept the reference? Now, with Immer, we can safely use .push() to push to an array:

state.addedIds.push(product.id);

state.quantityById[product.id] = 1;

return state;

This code snippet of three lines is equivalent to the previous one where we used spread operators and will work as expected. Introducing just a 190-byte plugin makes our code far more readable and maintainable.

Now you can give it a try and refactor the REMOVE_FROM_CART reducer where we used the clonedIds and clonedQuantityById variables, which aren't required with Immer.

Let's now refactor the SET_FAVORITE reducer located inside the shop model:

const products = [...state.products];

products[indexToModify] = product;

return {

  ...state,

  products,

};

We refactor it to this one-liner:

state.products[indexToModify] = product;

return state;

Since we added Immer, it isn't a requirement anymore to clone our arrays or objects to obtain a new reference. This is because Immer resolves this by creating a temporary draft state that is a proxy of the current state; so once we return the state, the mutations are transferred to the next state.

Give it a try and refactor the SET_QUERY and SET_PRODUCTS reducers, and if you get stuck, feel free to check the official Redux Made Easy with Rematch GitHub repository. When you think you're done, use yarn test to validate that the suite passes correctly.

The next section is going to be on the Rematch updated plugin, an interesting plugin for situations where we need to track when our promises are dispatched.

The Rematch updated plugin

The Rematch updated plugin is the easiest plugin that we're covering. Basically, it's a plugin for maintaining timestamps when an effect is dispatched.

It can be used to prevent expensive fetch requests within a certain time period or to throttle effects, for example, to avoid users clicking on the same button multiple times within a period of 3 seconds.

Installation and configuration

Install it like all the other official Rematch plugins:

yarn add @rematch/updated

The updated plugin also accepts some configuration with the following properties:

  • name: The name of the model to be created for storing the updated timestamps. By default, it is updated.
  • blacklist: An array of model names for which the plugin won't track effects.
  • dateCreator: You can pass any custom implementation to create Date objects with custom formatting or a time zone.

We are not going to use the plugin in our application because there's no use case in our application for it.

To conclude this chapter, we will look at the latest official Rematch plugin, the Rematch persist plugin. As the name indicates, this handles persisting our state in different storage services practically out of the box, such as the browser local storage.

The Rematch persist plugin

Last but not least is the Rematch persist plugin, a plugin of just 167 bytes for handling automatic state persistence. It's built on top of the redux-persist library (https://github.com/rt2zz/redux-persist). We can persist to hundreds of different storage services our whole Rematch store state.

Installation and configuration

Install it like all the other official Rematch plugins:

yarn add @rematch/persist redux-persist

The Rematch persist plugin also accepts some configuration through four arguments:

  • persistConfig: This is the first argument and is an object compatible with the config argument accepted by the redux-persist library.
  • nestedPersistConfig: Whenever you need to use a nested persist configuration for some models, we can provide an object with a mapping from the model's name to the redux-persist config for this model.
  • persistStoreConfig: The object compatible with the configuration argument accepted by the redux-persist persistStore method.
  • callback: A function called after rehydration is finished. Rehydration means when the redux-persist library has already transferred the data saved in the storage to the store.

To introduce this plugin in our application, there are two keys that are always required in our configuration file:

import createPersistPlugin from "@rematch/persist"

import localStorage from "redux-persist/lib/storage"

  ...

  plugins: [

    createLoadingPlugin(),

    createSelectPlugin(),

    createImmerPlugin(),

    createPersistPlugin({

      key: "cart-storage",

      storage: localStorage,

      whitelist: ["cart", "shop"],

    }),

  ],

});

The key property is always required since it will be used for defining the key name in the provided storage. Also, the storage allows us to pass custom storage options. In our case, we're going to use the native local storage provided by the browser.

Now, automatically, if we add some products to the cart and reload the page, we'll see that our cart state continues as we had left it before reloading. That's because our Rematch persist plugin persists the whole store inside browser storage after each change in our state. When the store is initialized back, it rehydrates the current state with the persisted one, making changes across our whole state completely persisted to changes on browser or network failures.

We have seen that adding plugins to Rematch is as easy as just installing and adding them to the plugins array of the init() method. They improve the productivity we got initially with Rematch without any inconvenience and make the development experience even better.

Summary

In this chapter, we have learned how Rematch plugins work internally. We've studied in depth which properties and hooks they expose and how those properties are used in real plugins. We have also learned which plugins are official Rematch plugins and how we can use them inside a Rematch application.

In the next chapter, we'll take another step along our learning curve with Rematch: we'll learn how to create a Rematch plugin from the ground up that will be used in our Amazhop application, and how to publish this plugin to NPM to contribute to the open source community.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.0.157