Skip to content
This repository has been archived by the owner on Jun 10, 2021. It is now read-only.

Redux thunk utility that aims to ease the process of retrying last failed action

License

Notifications You must be signed in to change notification settings

jkomyno/redux-error-snapshot

Repository files navigation

redux-error-snapshot

Redux thunk utility that aims to ease the process of retrying last failed action.


Travis npm npm Issue Stats

Since v1.0.1 this package follows the rules of Semantic versioning.

Table of Contents

Overview

Every time a dispatched action has an error property, redux-error-snapshot takes care of saving a snapshot of the error'd state in its reducer, which is exposed to the user, and also provides a set of utilities to try again the last failed action with the same exact arguments. The user can also decide to hide some actions (which have the property error) to the reducer: for more informations about this possibility, checkout reducerCreator.

Installation

  • yarn add redux-error-snapshot

Or, if you prefer using npm (but you shouldn't):

  • npm i -S redux-error-snapshot

It requires redux-thunk since this library leverages its getState() and dispatch() methods.

You also need reselect if you want to use the selector provided.

Motivation

Why did you create this library?, you may ask? The reason is that I found myself writing quite the same logic for handling the classic Generic Error: press to try again button / popup in different apps. When you continuosly repeat something, there's certainly room for improvement, so here's my shot with this little package, that hopefully will be usuful to somebody else as well.

API reference

import {
  resetErrorState,
  retryLastAction,
  reducerCreator,
  reducer as errorSnapshot,
  selector as getErrorSnapshot,
} from 'redux-error-snapshot';
  • Actions:

    • resetErrorState: ()
    • retryLastAction: (reducerName = 'errorSnapshot')
  • Reducer:

    • errorSnapshot: (state = INITIAL_STATE, action)
    • reducerCreator: (blacklist = [])
  • Selector:

    • getErrorSnapshot: (store.getState(), reducerName = 'errorSnapshot')

Reducer

import { reducer as errorSnapshot } from 'redux-error-snapshot';

You have to import it inside your store manually. The simplest way would be adding it as argument in your combineReducers function, like showed in the example below. For reference, take a look here.

// reducers.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import { reducer as errorSnapshot } from 'redux-error-snapshot';
import todo from './todo';

const rootReducer = combineReducers({
  errorSnapshot,
  todo,
});

export default rootReducer;
// store.js
import {
  createStore,
  applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';

const initialState = {};
const store = createStore(
  reducers,
  initialState,
  applyMiddleware(thunk),
);

export default store;

It contains the snapshot of the last catched error situation in the app, so you have:

  • error: the error message
  • action: the function that failed last time
  • args: the same arguments passed to the action that failed
  • meta: general object

The initialState of the reducer is defined as following:

initialState = {
  meta: {},
  args: [],
};

How to correctly populate the reducer:

  const yourThunkAction = (arg1, arg2) =>
    async (dispatch, getState) => {
      try {
        // your code here
      } catch (err) {
        // example meta structure
        const meta = {
          reason: err.message,
          trace: err.stack,
        };

        dispatch({
          type: 'YOUR_ERROR_TYPE',
          action: yourThunkAction,
          // you need and `error` string property to create a snapshot
          error: 'Oops, something went wrong while executing yourThunkAction',
          args: [arg1, arg2],
          meta,
        });
      }
    }

Note that you can store what you want in meta, and since v1.2.1 you can simply access its children without checking that meta isn't null first.

+import React, { PureComponent } from 'react';
+import { connect } from 'react-redux';
+import { retryLastAction } from 'redux-error-snapshot';
+
+class YourComponent extends PureComponent {
+  
+  getReason = () => {
+    const { meta } = this.props.errorSnapshot;
-    return meta && meta.reason;
+    return meta.reason;
+  }
+
+  // ...
+}
+
+const mapStateToProps = ({ errorSnapshot }) => ({
+  errorSnapshot,
+});
+
+export default connect(mapStateToProps, { retryLastAction })(YourComponent); 

reducerCreator

import { reducerCreator as errorSnapshotCreator } from 'redux-error-snapshot';

You can also decide to hide some actions which have the property error. You just need to define a blacklist array with the types' RegEx patterns. Every action whose type dispatched matches one of the patterns of the blacklist is automagically ignored by the reducer.

Note that reducerCreator has this name because it is a curried function which accepts an array of strings as only parameter, and returns the same reducer described above.

Example:

// reducers.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import { reducerCreator as errorSnapshotCreator } from 'redux-error-snapshot';
import todo from './todo';

const rootReducer = combineReducers({
  /*
  force redux-error-snapshot to ignore actions with types such as
  '@@redux-form/SUBMIT_FAILED'
  */
  errorSnapshot: errorSnapshotCreator(['@@redux-form/*']),
  form,
  todo,
});

export default rootReducer;

Actions

import {
  resetErrorState,
  retryLastAction,
} from 'redux-error-snapshot';

The available actions are:

  • resetErrorState: sets the errorSnapshot reducer to initialState, returning the type RESET_ERROR_STATE.
  • retryLastAction: dispatches the action saved in the errorSnapshot reducer. If you have decided to import the reducer with a name different than errorSnapshot, you can pass its name as argument.

Example:

import { reducer as MyErrorReducer } from 'redux-error-snapshot';
import { retryLastAction } from 'redux-error-snapshot';

// ...

dispatch(retryLastAction('MyErrorReducer')); // automatically resets the reducer state

Selector

import { selector as getErrorSnapshot } from 'redux-error-snapshot';

Selectors are an efficient and composable way of extracting data from a reducer (you can take a look at the awesome documentation provided by reselect about selectors). The first argument of a selector is familiar to many redux developers, since it expects store.getState(). The next argument however, is optional: again, if you have decided to import the reducer with a name different than errorSnapshot, you can pass its name as second argument.

Example:

Typings

This project uses Flow as its type system. It automatically exports *.flow files, but not *.ts. If you do know how to automatically export TypeScript bindings without writing the same types twice, please let me know by opening a Pull Request.

Available Scripts

  • clean: Deletes the compiled lib folder;
  • build: Runs the clean script, transpiles the code with babel to the lib folder and copies the flow references;
  • build:watch: Runs the build script in watch mode
  • lint: Runs eslint
  • flow: Verifies if there are flow errors;
  • test: Runs the test suites with jest;
  • test:watch: Runs the tests in watch mode;
  • test:cov: Runs the tests and displays coverage (which should't get below 100%!)
  • test:ci: Tests lint, flow, and jest errors

You can build your own light version of setting the env.targets property in .babelrc to "node": "current". The version deployed to npm requires NodeJS 6.11.3, which is the current LTS as of September 2017.

Related Projects

License

This project is MIT licensed.

About

Redux thunk utility that aims to ease the process of retrying last failed action

Resources

License

Stars

Watchers

Forks

Packages

No packages published