• Stars
    star
    182
  • Rank 211,154 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 9 years ago
  • Updated about 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Redux toolset for keeping all the side effects inside your reducers while maintaining their purity.

redux-side-effects

NPM version Dependencies Build status Downloads

What if your reducers were generators? You could yield side effects and return application state.

Believe it or not, but side effects are still tied with your application's domain. Ideally, you would be able to keep them in reducers. But wait! Everybody is saying that reducers must be pure! So yeah, just keep the reducer pure and reduce side effects as well.

Why?

Some people (I am one of them) believe that Elm has found the proper way how to handle side effects. Yes, we have a solution for async code in redux and it's redux-thunk but the solution has two major drawbacks:

  1. Application logic is not in one place, which leads to the state where business domain may be encapsulated by service domain.

  2. Unit testing of some use cases which heavy relies on side effect is nearly impossible. Yes, you can always test those things in isolation but then you will lose the context. It's breaking the logic apart, which is making it basically impossible to test as single unit.

Therefore ideal solution is to keep the domain logic where it belongs (reducers) and abstract away execution of side effects. Which means that your reducers will still be pure (Yes! Also hot-reloadable and easily testable). There are basically two options, either we can abuse reducer's reduction (which is basically a form of I/O Monad) or we can simply put a bit more syntactic sugar on it.

Because ES6 generators is an excellent way how to perform lazy evaluation, it's also a perfect tool for the syntax sugar to simplify working with side effects.

Just imagine, you can yield a side effect and framework runtime is responsible for executing it after reducer reduces new application state. This ensures that Reducer remains pure.

import { sideEffect } from 'redux-side-effects';

const loggingEffect = (dispatch, message) => console.log(message);

function* reducer(appState = 1, action) {
  yield sideEffect(loggingEffect, 'This is side effect');

  return appState + 1;
}

The function is technically pure because it does not execute any side effects and given the same arguments the result is still the same.

Usage

API of this library is fairly simple, the only possible functions are createEffectCapableStore and sideEffect. createEffectCapableStore is a store enhancer which enables us to use Reducers in form of Generators. sideEffect returns declarative Side effect and allows us easy testing. In order to use it in your application you need to import it, keep in mind that it's named import therefore following construct is correct:

import { createEffectCapableStore } from 'redux-side-effects'

The function is responsible for creating Redux store factory. It takes just one argument which is original Redux createStore function. Of course you can provide your own enhanced implementation of createStore factory.

To create the store it's possible to do the following:

import { createStore } from 'redux';
import { createEffectCapableStore } from 'redux-side-effects';

const reducer = appState => appState;

const storeFactory = createEffectCapableStore(createStore);
const store = storeFactory(reducer);

Basically something like this should be fully functional:

import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { createEffectCapableStore, sideEffect } from 'redux-side-effects';

import * as API from './API';

const storeFactory = createEffectCapableStore(createStore);

const addTodoEffect = (dispatch, todo) => API.addTodo(todo).then(() => dispatch({type: 'TODO_ADDED'}));

const store = storeFactory(function*(appState = {todos: [], loading: false}, action) {
  if (action.type === 'ADD_TODO') {
    yield sideEffect(addTodoEffect, action.payload);

    return {...appState, todos: [...appState.todos, action.payload], loading: true};
  } else if (action.type === 'TODO_ADDED') {
    return {...appState, loading: false};
  } else {
    return appState;
  }
});

render(<Application store={store} />, document.getElementById('app-container'));

Declarative Side Effects

The sideEffect function is just a very simple declarative way how to express any Side Effect. Basically you can only yield result of the function and the function must be called with at least one argument which is Side Effect execution implementation function, all the additional arguments will be passed as arguments to your Side Effect execution implementation function.

const effectImplementation = (dispatch, arg1, arg2, arg3) => {
  // Your Side Effect implementation
};


yield sideEffect(effectImplementation, 'arg1', 'arg2', 'arg3'....);

Be aware that first argument provided to Side Effect implementation function is always dispatch so that you can dispatch new actions within Side Effect.

Unit testing

Unit Testing with redux-side-effects is a breeze. You just need to assert iterable which is result of Reducer.

function* reducer(appState) {
  if (appState === 42) {
    yield sideEffect(fooEffect, 'arg1');

    return 1;
  } else {
    return 0;
  }
}

// Now we can effectively assert whether app state is correctly mutated and side effect is yielded.
it('should yield fooEffect with arg1 when condition is met', () => {
  const iterable = reducer(42);

  assert.deepEqual(iterable.next(), {
    done: false,
    value: sideEffect(fooEffect, 'arg1')
  });
  assert.equal(iterable.next(), {
    done: true,
    value: 1
  });
})

Example

There's very simple fully working example including unit tests inside example folder.

You can check it out by:

cd example
npm install
npm start
open http://localhost:3000

Or you can run tests by

cd example
npm install
npm test

Contribution

In case you are interested in contribution, feel free to send a PR. Keep in mind that any created issue is much appreciated. For local development:

  npm install
  npm run test:watch

You can also npm link the repo to your local Redux application so that it's possible to test the expected behaviour in real Redux application.

Please for any PR, don't forget to write unit test.

Need Help?

You can reach me on reactiflux - username tomkis1, or DM me on twitter.

FAQ

Does redux-side-effects work with working Redux application?

Yes! I set this as the major condition when started thinking about this library. Therefore the API is completely backwards compatible with any Redux application.

My middlewares are not working anymore, what has just happened?

If you are using middlewares you have to use createEffectCapableStore for middleware enhanced store factory, not vice versa. Meaning:

    const createStoreWithMiddleware = applyMiddleware(test)(createStore);
    const storeFactory = createEffectCapableStore(createStoreWithMiddleware);
    const store = storeFactory(reducer);

is correct.

Can I compose reducers?

Yes! yield* can help you with the composition. The concept is explained in this gist

I keep getting warning "createEffectCapableStore enhancer from redux-side-effects package is used yet the provided root reducer is not a generator function", what does that mean?

Keep in mind that your root reducer needs to be generator function therefore this will throw the warning:

const storeFactory = createEffectCapableStore(createStore);
const store = storeFactory(function(appState) { return appState; });

but this will work:

const storeFactory = createEffectCapableStore(createStore);
const store = storeFactory(function* (appState) { return appState; });

Can I use ()* => {} instead of function*()?

Unfortunately no. Only function* is valid ES6 syntax.

I am using combineReducers, how does this work with redux-side-effects?

If you are using standard Redux combineReducer in your application, please use the imported version from this package, original implementation does not work with generators. However, keep in mind that this method is opinionated and therefore you should probably provide your own implementation.

Usage is simple:

import { combineReducers } from 'redux-side-effects'

More Repositories

1

node-pg-migrate

Node.js database migration management for PostgreSQL
TypeScript
1,286
star
2

prism

React / Redux action composition made simple http://salsita.github.io/prism/
TypeScript
496
star
3

chrome-extension-skeleton

Minimal skeleton for Chrome extension
JavaScript
470
star
4

redux-saga-rxjs

RxJS implementation of Saga pattern for redux
JavaScript
113
star
5

jq-clipthru

CoffeeScript
88
star
6

chrome-extension-skeleton-ng

JavaScript
68
star
7

flux-boilerplate

Very simple flux boilerplate with stateless stores and effect reducers
JavaScript
58
star
8

geo-tree

High performance library for geographical map-related operations
JavaScript
52
star
9

inStyle

Modify the current selector &: https://instyle-css.salsitasoft.com
CSS
47
star
10

flask-raml

Flask-RAML (REST API Markup Language) API server with parameter conversion, response encoding, and examples.
Python
47
star
11

passthruapp

Updated version of Igor Tandetnik's PassthroughAPP
C
26
star
12

redux-elm-skeleton

Skeleton project for quick start with redux-elm
JavaScript
18
star
13

web-api-proxy

Proxy any request to a remote web API to avoid embedding secret keys in your client-side app
JavaScript
15
star
14

go-jira

JIRA REST API client library for Go (Golang)
Go
15
star
15

jquery-ui-scalebreaker

does cool stuff
JavaScript
14
star
16

generator-salsa

JavaScript
12
star
17

go-pivotaltracker

Pivotal Tracker API client for Go (Golang)
Go
11
star
18

backbone-schema

JavaScript
11
star
19

shishito

Python module for selenium webdriver test execution
Python
10
star
20

redux-form-actions

JavaScript
10
star
21

dripping-bucket

Library to calculate delays for operations running against rate-limited services
JavaScript
10
star
22

ng2-if-media

TypeScript
9
star
23

grunt-package-minifier

Minimizes space taken by node modules and their dependencies.
JavaScript
9
star
24

Magpie

CommonJS partial implementation for Windows Active Script (JScript)
C++
8
star
25

scalable-frontend-with-cyclejs

Scalable frontend with Cycle.js
JavaScript
7
star
26

flask-config

Flask configuration class.
Python
7
star
27

spicy-hooks

TypeScript
7
star
28

postcss-inrule

https://instyle-css.salsitasoft.com/
JavaScript
7
star
29

react-beacon

Onboarding tooltips for web apps using Slack-like beacons
JavaScript
7
star
30

grunt-userev

JavaScript
6
star
31

RestQ

Declarative way to get data from RESTful APIs.
CoffeeScript
6
star
32

browser-require

Implementation of CommonJS require() function for use in client-side environments where synchronous loading is appropriate (e.g. browser extension).
JavaScript
6
star
33

react-training

react training app
TypeScript
5
star
34

jenkins-docker-skeleton

Test your app in Jenkins using Docker
Shell
5
star
35

mastermind

Mastermind (board game) with all the cool stuff packed in, namely redux-saga, normalizr, reselect and react-router.
JavaScript
5
star
36

todo2issue

CLI tool to synchronize in-code TODOs to GitHub issues
TypeScript
4
star
37

bunny-migrate

CLI tool for managing RabbitMQ schema instances
JavaScript
4
star
38

xml2js-schema

Extends node-xml2js with schema validation support based on JSON schema
JavaScript
4
star
39

react-devstack

An ultimate development stack built on top of React, Redux, Router5, Redux-Saga, React-Helmet
JavaScript
4
star
40

pyraml

RAML (REST API Markup Language) enhanced loader, parameter converter, and API wrapper.
Python
4
star
41

backbone-xml

JavaScript
4
star
42

go-sprintly

Sprintly API client for Go (Golang)
Go
3
star
43

winunit

Ready to use WinUnit for C++ unit testing
C++
3
star
44

foosball-rating

Keeps track of foosball player's rating based on augmented chess elo rating
JavaScript
3
star
45

flask-ecstatic

Serves static files with optional directory index.
Python
3
star
46

Serrano

Simple web data extraction language.
JavaScript
3
star
47

nodejs-training

backed part for our frontend (react/angular) trainings
JavaScript
3
star
48

mastermind-server

JavaScript
3
star
49

redux-ducks

Redux toolset for isolating state as defined by ducks-modular-redux proposal.
JavaScript
3
star
50

nodejs-modules

reusable modules for node.js backed servers / services
JavaScript
2
star
51

versionserver

A servlet providing auto-incremented build numbers for projects being built on Jenkins or other environments.
Python
2
star
52

angular-training

angular training app
TypeScript
2
star
53

iesetuphelper

Shared library containing helper functions for Windows setup packages.
C++
2
star
54

siros-postgres

New generation of HW / budget tracking system.
JavaScript
2
star
55

redux-prism

new name for redux-elm
2
star
56

grunt-md-templator

Take a Grunt template, add a hint of Markdown, shake and serve as HTML.
CoffeeScript
2
star
57

node_installer

Shell
2
star
58

crx-utils

Utilities for working with Chrome extension (.crx) files.
JavaScript
2
star
59

flask-run

Flask-based web application runner.
Python
2
star
60

salsita-dancing-santa

Celebrate the holiday season with Santa dancing to the dulcet tones of Wham!
CSS
2
star
61

pwa-cordova-meetup-app

Demo application for Salsita Meet Up May 14, 2019
Java
2
star
62

pydataloader

Extensible JSON/YAML/RAML/... data loader for Python.
Python
2
star
63

express-angular-skeleton

Skeleton for our new Express & Angular projects.
CSS
1
star
64

ng-modules

reusable modules for angular apps
TypeScript
1
star
65

wrapperElement

CSS
1
star
66

angular2-migration

JavaScript
1
star
67

jquery-viewable

lightweight jQuery extension that determines if a specific element is visible on a page
JavaScript
1
star
68

runtime-config

JavaScript
1
star
69

cci-pingu

Periodically check for new builds (artifacts) on CircleCI and install them in turn.
JavaScript
1
star
70

react-modules

reusable modules for react / redux apps
1
star
71

flask-serverinfo

Flask server info view for inspecting server app and user requests.
Python
1
star
72

angularjs-cayenne

JavaScript
1
star
73

metros-powerup

Card pointing power up for Trello
JavaScript
1
star
74

salsita.github.io

HTML
1
star
75

left-pad-service

Demo application for Salsita presentation on 2016/11/24
JavaScript
1
star
76

cordova-node-api

JavaScript
1
star
77

shishito-sample-project

Sample project for Salsa WebQA library
Python
1
star
78

bloggo

Poet & Angular based blagging app.
JavaScript
1
star