redux-saga-factory
TypeScript icon, indicating that this package has built-in type declarations

1.1.0 • Public • Published

redux-saga-factory 🏭

Next generation sagas using classes and decorators

codecov CircleCI semantic-release

Introduction

Sagas are great, but they don’t always fit nicely inside large scale projects since:

  1. They are nothing more than generator functions, so it is impossible to use them with dependency injection patterns.
  2. You often have to type repeated boilerplate only to wrap the actual action handler.

This library will allow you to:

  1. Create sagas using factory classes.
  2. Leverage decorators to yield common saga patterns (hence removing boilerplate).

Installation

yarn install redux-saga-factory reflect-metadata

Usage

1. Create Saga using class & decorators

import 'reflect-metadata'
import { take, SagaFactory } from 'redux-saga-factory'

export class KickassSaga extends SagaFactory {

    @take("some-action")
    *onSomeAction(action) {
        const result = yield call(fetchSomething);
        return result.data;
    }
}

2. Use the saga

import { KickassSaga } from "./KickassSaga.ts";

// Setup the factory 
const kickassSaga = new KickassSaga(/* dependencies? */);

// Get the sagas
const sagas = kickassSaga.getSagas();

// sagas === { onSomeAction: [Function: onSomeAction]}

// Create the store as usual
import { Action, createStore, applyMiddleware } from "redux";
import sagaMiddlewareFactory from "redux-saga";

const sagaMiddleware = sagaMiddlewareFactory();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));

Object.values(saga).forEach(item => sagaMiddleware.run(item));

⚠️ You must import "reflect-metadata" polyfill and you should only import it once in the code.

Deeper look

Say you have this saga:

function* someSaga() {
  while (true) {
    const action = yield take("some_action");
    yield call(sagaWorker, action);
  }
}

function* sagaWorker(action) {
  // do the magic
}

We can change it to this

class SomeSaga {
  @take("some_action")
  static *sagaWorker(payload) {
    // do the magic
  }
}

// and use it by...

const sagas = SagaFactory.fromMetadata(SomeSaga);

// sagas = { some_action_saga: [Function: sagaWorker] }

Or, if we want to add dependency injection:

class SomeSaga extends SagaFactory {

	private readonly logger :ILogger;

	constructor(logger:ILogger){
		this.logger = logger;
	}

	@take("some_action")
	*sagaWorker(payload) {
		this.logger.debug("saga with di!")
		// do the magic
	}

}

// and use it by...

const factory = new SomeSaga(loggerInsatance);

const sagas = factory.getSagas()

// sagas = { some_action_saga: [Function: sagaWorker] }

You can also use the Factory to generate multiple sagas or use different effect patterns:

class MultiSaga extends SagaFactory {

	@take("some_action")
	*sagaWorker(payload) {
		logger.debug("saga with di!")
		// do the magic
	}

	@takeLatest("some_action")
	*anotherSagaWorker(payload) {
		// using takeLatest effect instead of take
	}
}

Use with typescript-fsa:

This library is 100% compatible with typescript-fsa, which provides "type-safe experience with Flux actions with minimum boilerplate".

import { actionCreatorFactory } from 'typescript-fsa'

const actionCreator = actionCreatorFactory();
const fsaAction = actionCreator<{ foo: string }>("SUBMIT_ACTION");

class FsaSaga extends SagaFactory {

	@take(fsaAction)
	*sagaWorker(payload) {
		// do the magic
	}

}

Use with typescript-fsa-redux-saga

Another great library is typescript-fsa-redux-saga which easily wraps sagas with async actions. Resulting saga dispatches started action once started and done/failed upon finish.

This can be achieved automatically with redux-saga-factory by passing the AsyncAction creator as an argument:

import { actionCreatorFactory } from 'typescript-fsa'

const actionCreator = actionCreatorFactory();
const fsaAction = actionCreator<{ foo: string }>("SUBMIT_ACTION");
const fsaAsyncAction = actionCreator.async<{ foo: string }, { bar: string}>("SUBMIT_ASYNC_ACTION");

class FsaSaga extends SagaFactory {

	@take(fsaAction, fsaAsyncAction)
	*sagaWorker(payload) {
		// do the magic
	}

}

// store.dispatch(fsaAction({foo: 'bar'}))
//
// Actions fired:
//
// 1. SUBMIT_ACTION
// 2. SUBMIT_ASYNC_ACTION_STARTED
// --- Here sagaWorker will be called ---
// 3. SUBMIT_ASYNC_ACTION_DONE

Use without class

TBA

Use with custom saga pattern

TBA

Readme

Keywords

none

Package Sidebar

Install

npm i redux-saga-factory

Weekly Downloads

0

Version

1.1.0

License

MIT

Unpacked Size

261 kB

Total Files

42

Last publish

Collaborators

  • iqoqoservice