Need private packages and team management tools?Check out npm Teams »

@mrnkr/redux-saga-toolbox

2.0.0 • Public • Published

Redux-Saga Toolbox

NPM version Downloads

A set of utilities meant to have you write less and do more, better.

It's pretty well tested (99.07% code coverage which would be 100% if I had been able to make the istanbul comments work for those while (true) lines 😅). For now, either take my word for it or clone the repo and run yarn test but I'm learning how to use codecov so that I can put a badge proving this really soon 🤓

Motivation

Since I started using redux-saga (which I use alongside reduxsauce) I noticed something. Not only was I writing the same logic over and over again but I was also seeing all the devs in the project do things their own way, for better or for worse. We were following no structure nor were we reutilizing logic in any way. How does one solve such a problem? Taking the best from ngrx/entity and creating factory functions whose output are the saga handlers. I like fractal as far as file structures are concerned, by auto generating sagas I can keep the logic within one file without the file getting too big, hence, I can use fractal and be happy :)

Installation

yarn add @mrnkr/redux-saga-toolbox

Quick start

There is a lot going on here, starting won't be too quick... Sorry hehe. Let's go through all possible use cases (at least the ones I could come up with).

Single event sagas (the ones that are not observables)

The most basic of basic sagas

What did I consider a basic saga? One which is triggered by a request action and dispatches a loading, a commit and a success action. It may dispatch an error action if it needs to. The way to make one of those is the following:

import { createSingleEventSaga } from '@mrnkr/redux-saga-toolbox';
import APIClient from 'my-api';
 
const watcher = createSingleEventSaga({
  takeEvery: 'REQUEST',
  loadingAction: () => ({ type: 'LOADING' }),
  commitAction: payload => ({ payload, type: 'COMMIT' }),
  successAction: payload => ({ payload, type: 'SUCCESS' }),
  errorAction: error => ({ error, type: 'ERROR' }),
  action: APIClient.getMeThatData,
});

Basically, just specify the action you want to use as the request action in the takeEvery property. Then specify the action creators for the saga to put and lastly specify some function to execute as the long running action that represents the center of it all, like... your API call!

Also, you may fancy using an alternative way of providing your takeEvery parameter. An equivalent way of listening to the same action is takeEvery: action => action.type === 'REQUEST'. Why is this useful? Well, say you have multiple actions and they should all trigger the same saga, this can be achieved in the following fashion: takeEvery: action => action.type === actionType1 || action.type === actionType2. This I added because I needed it, hope some of you will also find it useful!

Task cancellation

Take the same basic saga. Say the download is taking longer than expected and you want to cancel it. Just dispatch the cancel action you specified in the configuration like so:

const watcher = createSingleEventSaga({
  ...restOfTheConfiguration,
  cancelActionType: 'CANCEL',
});
Uploading data with optimistic feedback

Do you enjoy using Google apps? Do you like how they allow you to undo actions instead of making you confirm everything before it gets done? Well, that undo mechanism can easily be implemented with redux-saga-toolbox.

const watcher = createSingleEventSaga({
  ...restOfTheConfiguration,
  runAfterCommit: true,
  undoThreshold: 5000,
  undoActionType: 'REQUEST_UNDO',
  undoAction: (payload) => ({ payload, type: 'UNDO' }),
  undoPayloadBuilder?: (args) => { return someProcessedVersionOfTheArgs; },
});

Let us dive a bit more into detail... By setting runAfterCommit to true we're saying that the commitAction() should be dispatched with the action payload and before the action gets run, hence, before the data gets processed by the API. The undoThreshold is the time the user has to undo the action before it is made definitive, in this example I set it to 5 seconds. Last but not least, undoActionType is the action which we will have to dispatch in order to trigger the undoing of the action which will cut the execution of it and also dispatch the undoAction with a payload equal to the return value of undoPayloadBuilder. You don't need to provide the last function, it defaults to the identity function (returns what it receives). Also, you should know it is a generator function, it returns stuff (if it doesn't it breaks stuff), but it is still a generator function.

Something else you may find useful is that if you define an undoAction you may set the undoOnError flag to true and that will allow you to not have your redux store get corrupt in case of failures.

Processing the action payload and the API call result

I wanted to give up on as little flexibility as I possibly could. That means I wanted to be able to process the payload received from the action that triggered the saga and also I wanted to process the result the API gave me. To do that I exposed two hooks which are generator functions which are expected to return the processed payload or result.

This is how they're meant to be used:

const watcher = createSingleEventSaga({
  ...restOfTheConfiguration,
  beforeAction: (args) => { return processedArgs; },
  afterAction: (res, args) => { return processedResult; },
});

Some details to take into consideration: beforeAction gets run before commit regardless of whether the action runs before or after it. If beforeAction is provided and runAfterCommit is set to true then commitAction() will have the processed payload instead of the one it received at first. afterAction receives the result of the API call and as a second (optional) parameter it receives the payload (as returned by beforeAction).

Crappy API? Retry!

Redux saga lets us easily retry stuff, I did not use that but I still offer the same possiblity in this library. If you set retry to any number greater than 0 (0 is the default value) you will have let your API fail on you without you giving up on it... Good guy you!

const watcher = createSingleEventSaga({
  ...restOfTheConfiguration,
  retry: 3,
});
Taking too long to respond? Timeout

Same logic as above. Nice thing about this? You can use the timeout property in conjunction with the retry property and give each try a maximum time to complete. Effortlessly, by the way 😍

// try 3 times at most but don't let
// each try take longer than 800ms
const watcher = createSingleEventSaga({
  ...restOfTheConfiguration,
  retry: 3,
  timeout: 800,
});

Changelog

  • 1.0.0 - First release, had some trouble with config files. That's why the actual first release was 1.0.2 😬
  • 1.0.3 - If you were one of the few amazing people that downloaded the library as soon as I released it you may have noticed inconsistencies in the documentation... I tried to fix all the problems I could find in this version... Sorry!!
  • 1.0.8 - Updated the documentation to fix some discrepancies and fixed the forms reducer so that it does not re-register a form.
  • 1.0.9 - Updated selectors to be memoized.
  • 1.0.10 - Updated saga generator typings to support predicates as takeEvery and subscribe actions. If you're like me you wanted this to trigger the same saga with multiple actions.
  • 1.0.11 - Added support for initial values in the forms module.
  • 1.0.12 - Added support for not clearing forms on submit.
  • 1.0.13 - Added support for multiple submissions on the same form (sorry if you had to deal with this error 😞)
  • 1.0.14 - Fixed bug in addAll in entity adapter (both sorted and unsorted) (having bugs there means that I did not copy the whole thing carelessly, just remade it carelessly 😛)
  • 1.0.15 - Fixed bug that when a form had an error the saga stopped running.
  • 1.0.16 - Fixed bug in form saga - stops listening when the form is cleared.
  • 1.0.18 - Deprecated entity module and forms module. To use entity I encourage you find a way to do it yourself, to manage forms I recommend formik.
  • 2.0.0 - Removed deprecated modules

The boy scout rule

It's not enough to write code well. The code has to be kept clean over time. We've all seen code rot and degrade as time passes. So we must take an active role in preventing that degradation.

The boy scouts of America have a simple rule that we can apply to our profession.

Leave the campground better than you found it.

If we all checked-in out code a little cleaner than we checked it out, the code simply could not rot. The cleanup doesn't have to be something big. Change one variable name for the better, break up one function that's a little too large, eliminate one small bit of duplication, clean up one composite if statement.

Can you imagine working on a project where the code simply got better as time passed? Do you believe that any other option is professional? Indeed, isn't continuous improvement an intrinsic part of professionalism?

Robert C. Martin - from the book Clean Code (he says the took this from another book but I didn't take note which one)

Install

npm i @mrnkr/redux-saga-toolbox

DownloadsWeekly Downloads

8

Version

2.0.0

License

MIT

Unpacked Size

78.1 kB

Total Files

56

Last publish

Collaborators

  • avatar