Skip to content

Instantly share code, notes, and snippets.

@burdiuz
Last active December 15, 2019 17:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save burdiuz/8769f01ecb45c4fcceb76ef0bc8a1c76 to your computer and use it in GitHub Desktop.
Save burdiuz/8769f01ecb45c4fcceb76ef0bc8a1c76 to your computer and use it in GitHub Desktop.
Redux Side Effects

@actualwave/redux-side-effect

Very simple to use middleware for redux to make side effects for actions. Inspired by redux-saga but not uses generators, instead relies on async functions and can be used with redux-thunk.

How to use

  1. Register middleware
import sideEffectMiddleware from '@actualwave/redux-side-effect';

const store = createStore(
 myReducers,
 applyMiddleware(sideEffectMiddleware),
);
  1. Add side effects
import { take, put, select, call } from '@actualwave/redux-side-effect';  

take('FETCH_DATA', async (action) => {
  const { data } = await axios.get('/something');
  
  put({ type: 'FETCH_DATA_SUCCESS', payload: data });
});

take('POST_DATA', async (action) => {
  // ....
});

take('PATCH_DATA', async (action) => {
  // ....
});

That's it.

Big gotcha that may save you from a headache or sleepless night

This middleware will work with single redux store, it's not a problem for most of the projects because developers usually create one redux store per application. But for projects that may be using multiple stores and this middleware, they have to generate middlewares for each store, like this:
main-redux-store.js

import { createNamespace } from '@actualwave/redux-side-effect';

const { put, call, select, take, middleware } = createNamespace();

const store = createStore(
  mainReducers,
  applyMiddleware(middleware),
);

export { put, call, select, take };

sub-redux-store.js

import { createNamespace } from '@actualwave/redux-side-effect';

const { put, call, select, take, middleware } = createNamespace();

const store = createStore(
  subReducers,
  applyMiddleware(middleware),
);

export { put, call, select, take };

With such setup you could use separate sets of functions for different stores side-effects.js

import * as main from 'main-redux-store.js';
import * as sub from 'sub-redux-store.js';

main.take('ACTION_TYPE', () => main.put({ type: 'ACTION_SUCCESS' }))

const createSubEffect = ({ take, put }) => {
 take('GENERAL_ACTION_TYPE', () => put({ type: 'GENERAL_ACTION_SUCCESS' }));
};

createSubEffect(main);
createSubEffect(sub);

An example:

import sideEffectMiddleware, { take, put, select, call } from '@actualwave/redux-side-effect';

const store = createStore(
  myReducers,
  compose(
    applyMiddleware(thunkMiddleware, sideEffectMiddleware),
    // Redux Tool Google Chrome Extension setup
    window.__REDUX_DEVTOOLS_EXTENSION__
      ? window.__REDUX_DEVTOOLS_EXTENSION__()
      : compose,
  ),
);

// Side effect function could be a normal function too
take('action-type', (action) => {
  console.log('MY ACTION TYPE INTERCEPTED:', action);

  put({ type: 'side-effect-success' });
  
  select((store) => console.log(store));
});

take('async-action-type', async (action) => {
  console.log('MY ACTION TYPE INTERCEPTED:', action);

  const value = await call(myFunction, 'arg1', 'arg2', 'arg3');
  
  await put({ type: 'side-effect-success', payload: { value } });
  
  select((store) => console.log(store));
});
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const { createMapOfSets } = require('@actualwave/map-of-sets');
const createNamespace = () => {
const handlers = createMapOfSets();
let store;
const put = (...args) => store.dispatch(...args);
const select = (selector, ...args) => selector(store.getState(), ...args);
const call = (fn, ...args) => fn(...args);
const take = (actionType, actionHandler) => {
handlers.add(actionType, actionHandler);
};
const middleware = (storeArg) => {
store = storeArg;
return (next) => (action) => {
const { type } = action;
handlers.eachValue(type, (handler) => handler(action));
next(action);
};
};
return {
put,
call,
select,
take,
middleware,
};
};
const { put, call, select, take, middleware } = createNamespace();
exports.put = put;
exports.call = call;
exports.select = select;
exports.take = take;
exports.middleware = middleware;
exports.createNamespace = createNamespace;
exports.default = middleware;
{
"name": "@actualwave/redux-side-effect",
"description": "Simple redux middleware inspired by redux-saga to use with async/await",
"version": "0.0.2",
"main": "index.js",
"keywords": [
"redux",
"middleware",
"async",
"await",
"side effect",
"function",
"promise"
],
"author": {
"name": "Oleg Galaburda",
"email": "burdiuz@gmail.com",
"url": "http://actualwave.com/"
},
"bugs": {
"url": "https://gist.github.com/burdiuz/8769f01ecb45c4fcceb76ef0bc8a1c76",
"email": "burdiuz@gmail.com"
},
"homepage": "https://gist.github.com/burdiuz/8769f01ecb45c4fcceb76ef0bc8a1c76",
"dependencies": {
"@actualwave/map-of-sets": "0.0.2"
},
"license": "MIT"
}
import { createMapOfSets } from '@actualwave/map-of-sets';
export const createNamespace = () => {
const handlers = createMapOfSets();
let store;
const put = (...args) => store.dispatch(...args);
const select = (selector, ...args) => selector(store.getState(), ...args);
const call = (fn, ...args) => fn(...args);
const take = (actionType, actionHandler) => {
handlers.add(actionType, actionHandler);
};
const middleware = (storeArg) => {
store = storeArg;
return (next) => (action) => {
const { type } = action;
handlers.eachValue(type, (handler) => handler(action));
next(action);
};
};
return {
put,
call,
select,
take,
middleware,
};
};
const { put, call, select, take, middleware } = createNamespace();
export { put, call, select, take, middleware };
export default middleware;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment