MCPcopy
hub / github.com/jeffbski/redux-logic

github.com/jeffbski/redux-logic @v5.0.2 sqlite

repository ↗ · DeepWiki ↗ · release v5.0.2 ↗
125 symbols 387 edges 37 files 2 documented · 2%
README

redux-logic

"One place for all your business logic and action side effects"

Redux middleware that can:

  • intercept (validate/transform/augment) actions AND
  • perform async processing (fetching, I/O, side effects)

Build Status Known Vulnerabilities NPM Version Badge

tl;dr

With redux-logic, you have the freedom to write your logic in your favorite JS style:

  • plain callback code - dispatch(resultAction)
  • promises - return axios.get(url).then(...)
  • async/await - result = await fetch(url)
  • observables - ob$.next(action1)

Use the type of code you and your team are comfortable and experienced with.

Leverage powerful declarative features by simply setting properties:

  • filtering for action type(s) or with regular expression(s)
  • cancellation on receiving action type(s)
  • use only response for the latest request
  • debouncing\
  • throttling
  • dispatch actions - auto decoration of payloads

Testing your logic is straight forward and simple. redux-logic-test provides additional utilities to make testing a breeze.

With simple code your logic can:

  • intercept actions before they hit the reducer
  • validate, verify, auth check actions and allow/reject or modify actions
  • transform - augment/enhance/modify actions
  • process - async processing and dispatching, orchestration, I/O (ajax, REST, subscriptions, GraphQL, web sockets, ...)

Redux-logic makes it easy to use code that is split into bundles, so you can dynamically load logic right along with your split UI.

Server rendering is simplified with redux-logic since it lets you know when all your async fetching is complete without manual tracking.

Inspired by redux-observable epics, redux-saga, and custom redux middleware, redux-logic combines ideas of each into a simple easy to use API.

Quick Example

This is an example of logic which will listen for actions of type FETCH_POLLS and it will perform ajax request to fetch data for which it dispatches the results (or error) on completion. It supports cancellation by allowing anything to send an action of type CANCEL_FETCH_POLLS. It also uses take latest feature that if additional FETCH_POLLS actions come in before this completes, it will ignore the outdated requests.

The developer can just declare the type filtering, cancellation, and take latest behavior, no code needs to be written for that. That leaves the developer to focus on the real business requirements which are invoked in the process hook.

import { createLogic } from 'redux-logic';

const fetchPollsLogic = createLogic({
  // declarative built-in functionality wraps your code
  type: FETCH_POLLS, // only apply this logic to this type
  cancelType: CANCEL_FETCH_POLLS, // cancel on this type
  latest: true, // only take latest

  // your code here, hook into one or more of these execution
  // phases: validate, transform, and/or process
  process({ getState, action }, dispatch, done) {
    axios
      .get('https://survey.codewinds.com/polls')
      .then((resp) => resp.data.polls)
      .then((polls) => dispatch({ type: FETCH_POLLS_SUCCESS, payload: polls }))
      .catch((err) => {
        console.error(err); // log since could be render err
        dispatch({ type: FETCH_POLLS_FAILED, payload: err, error: true });
      })
      .then(() => done()); // call done when finished dispatching
  }
});

Since redux-logic gives you the freedom to use your favorite style of JS code (callbacks, promises, async/await, observables), it supports many features to make that easier, explained in more detail

Table of contents

Updates

Full release notes of breaking and notable changes are available in releases. This project follows semantic versioning.

A few recent changes that are noteworthy:

v2.0.0

Updated to RxJS@6. Your logic code can continue to use RxJS@5 until you are ready to upgrade to 6.

Optimizations to reduce the stack used, especially if a subset of features is used.

v1.0.0

Transpilation switched to Babel 7 and Webpack 4

v0.12

These changes are not breaking but they are noteworthy since they prepare for the next version which will be breaking mainly to remove the single dispatch version of process hook which has been a source of confusion.

  • Single dispatch signature for process hook is deprecated and warns in development build. This is when you use the signature process(deps, dispatch) (including dispatch but not done). To migrate change your use to include done process(deps, dispatch, done) and call the done cb when done dispatching.
  • New option warnTimeout defaults to 60000 (ms == one minute) which warns (in development build only) when the logic exceeds the specified time without completion. Adjust this value or set it to 0 if you have logic that needs to exceed this time or purposefully never ends (like listening to a web socket)

Goals

  • organize business logic keeping action creators and reducers clean
  • action creators are light and just post action objects
  • reducers just focus on updating state
  • intercept and perform validations, verifications, authentication
  • intercept and transform actions
  • perform async processing, orchestration, dispatch actions
  • wrap your core business logic code with declarative behavior
  • filtered - apply to one or many action types or even all actions
  • cancellable - async work can be cancelled
  • limiting (like taking only the latest, throttling, and debouncing)
  • features to support business logic and large apps
  • have access to full state to make decisions
  • easily composable to support large applications
  • inject dependencies into your logic, so you have everything needed in your logic code
  • dynamic loading of logic for splitting bundles in your app
  • your core logic code stays focussed and simple, don't use generators or observables unless you want to.
  • create subscriptions - streaming updates
  • easy testing - since your code is just a function it's easy to isolate and test

Usage

redux-logic uses rxjs@6 under the covers and to prevent multiple copies (of different versions) from being installed, it is recommended to install rxjs first before redux-logic. That way you can use the same copy of rxjs elsewhere.

If you are never using rxjs outside of redux-logic and don't plan to use Observables directly in your logic then you can skip the rxjs install and it will be installed as a redux-logic dependency. However if you think you might use Observables directly in the future (possibly creating Observables in your logic), it is still recommended to install rxjs separately first just to help ensure that only one copy will be in the project.

The rxjs install below npm install rxjs@^6 installs the lastest 6.x.x version of rxjs.

npm install rxjs@^6 --save  # optional see note above
npm install redux-logic --save
// in configureStore.js
import { createLogicMiddleware } from 'redux-logic';
import rootReducer from './rootReducer';
import arrLogic from './logic';

const deps = { // optional injected dependencies for logic
  // anything you need to have available in your logic
  A_SECRET_KEY: 'dsfjsdkfjsdlfjls',
  firebase: firebaseInstance
};

const logicMiddleware = createLogicMiddleware(arrLogic, deps);

const middleware = applyMiddleware(
  logicMiddleware
);

const enhancer = middleware; // could compose in dev tools too

export default function configureStore() {
  const store = createStore(rootReducer, enhancer);
  return store;
}


// in logic.js - combines logic from across many files, just
// a simple array of logic to be used for this app
export default [
 ...todoLogic,
 ...pollsLogic
];


// in polls/logic.js
import { createLogic } from 'redux-logic';

const validationLogic = createLogic({
  type: ADD_USER,
  validate({ getState, action }, allow, reject) {
    const user = action.payload;
    if (!getState().users[user.id]) { // can also hit server to check
      allow(action);
    } else {
      reject({ type: USER_EXISTS_ERROR, payload: user, error: true })
    }
  }
});

const addUniqueId = createLogic({
  type: '*',
  transform({ getState, action }, next) {
    // add unique tid to action.meta of every action
    const existingMeta = action.meta || {};
    const meta = {
      ...existingMeta,
      tid: shortid.generate()
    },
    next({
      ...action,
      meta
    });
  }
});

const fetchPollsLogic = createLogic({
  type: FETCH_POLLS, // only apply this logic to this type
  cancelType: CANCEL_FETCH_POLLS, // cancel on this type
  latest: true, // only take latest
  process({ getState, action }, dispatch, done) {
    axios.get('https://survey.codewinds.com/polls')
      .then(resp => resp.data.polls)
      .then(polls => dispatch({ type: FETCH_POLLS_SUCCESS,
                                payload: polls }))
      .catch(err => {
             console.error(err); // log since could be render err
             dispatch({ type: FETCH_POLLS_FAILED, payload: err,
                        error: true })
      })
      .then(() => done());
  }
});

// pollsLogic
export default [
  validationLogic,
  addUniqueId,
  fetchPollsLogic
];

processOptions introduced for redux-logic@0.8.2 allowing for even more streamlined code

processOptions has these new properties which affect the process hook behavior:

  • dispatchReturn - the returned value of the process function will be dispatched or if it is a promise or observable then the resolve, reject, or observable values will be dispatched applying any successType or failType logic if defined. Default is determined by arity of process fn, true if dispatch not provided, false otherwise. Details

  • successType - dispatch this action type using contents of dispatch as the payload (also would work with with promise or observable). You may alternatively provide an action creator function to use instead and it will receive the value as only parameter. Default: undefined.

  • if successType is a string action type

    • create action using successType and provide value as payload. ex: with successType:'FOO', result would be { type: 'FOO', payload: value }
  • if successType is an action creator fn receiving the value as only parameter

    • use the return value from the action creator fn for dispatching ex: successType: x => ({ type: 'FOO', payload: x })
    • if the action creator fn returns a falsey value like undefined then nothing will be dispatched. This allows your action creator to control whether something is actually dispatched based on the value provided to it.
  • failType - dispatch this action type using contents of error as the payload, sets error: true (would also work for rejects of promises or error from observable). You may alternatively provide an action creator function to use instead which will receive the error as the only parameter. Default: undefined.

  • if failType is a string action type

    • create action using failType, provide value as the payload, and set error to true. ex: with failType:'BAR', result would be { type: 'BAR', payload: errorValue, error: true }
  • if failType is an action creator function receiving the error value as its only parameter

    • use the return value from the action creator fn for dispatching. ex: failType: x => ({ type: 'BAR', payload: x, error: true })
    • if the action creator fn returns a falsey value like undefined then nothing will be dispatched. This allows your action creator to control whether something is actually dispatched based on the value provided to it.

The successType and failType would enable clean code, where you can simply return a promise or observable that resolves to the payload and rejects on error. The resulting code doesn't have to deal with dispatch and actions directly.

```js import { createLogic } from 'redux-logic';

const fetchPollsLogic = createLogic({ // declarative built-in functionality wraps your code type: FETCH_POLLS, // only apply this logic to this type cancelType: CANCEL_FETCH_POLLS, // cancel on this type latest: true, // only take latest

processOptions: { // optional since the default is true when dispatch is omitted from // the process fn signature dispatchReturn: true, // use returned/resolved value(s) for dispatching // provide action types or action creator functions to be used // with the resolved/rejected values from promise/observable returned successType: FETCH_POLLS_SUCCESS, // dispatch this success act type failType: FETCH_POLLS_FAILED // dispatch this failed action type },

Extension points exported contracts — how you extend this code

CreateLogic (Interface)
(no doc)
definitions/logic.d.ts
LogicMiddleware (Interface)
(no doc)
definitions/middleware.d.ts
ActionBasis (Interface)
(no doc)
definitions/action.d.ts
ActionCreator (Interface)
(no doc)
test/typecheck.createLogic.ts
TestPayload (Interface)
(no doc)
test/typecheck.action.ts
Dependency (Interface)
(no doc)
test/typecheck.middleware.ts
Dependency (Interface)
(no doc)
test/typecheck.ts
Base (Interface)
(no doc)
definitions/logic.d.ts

Core symbols most depended-on inside this repo

whenComplete
called by 279
definitions/middleware.d.ts
createLogic
called by 262
src/createLogic.js
createLogicMiddleware
called by 194
src/createLogicMiddleware.js
mw
called by 187
src/createLogicMiddleware.js
dispatch
called by 117
src/createDispatch.js
allow
called by 40
src/createLogicAction$.js
done
called by 34
src/createDispatch.js
reject
called by 30
src/createLogicAction$.js

Shape

Function 102
Interface 17
Method 6

Languages

TypeScript100%

Modules by API surface

src/createLogicAction$.js9 symbols
test/typecheck.createLogic.ts8 symbols
src/createDispatch.js8 symbols
test/createLogicMiddleware-process.spec.js7 symbols
test/createLogicMiddleware-deps.spec.js7 symbols
test/createLogic.spec.js7 symbols
definitions/middleware.d.ts7 symbols
test/createLogicMiddleware.spec.js6 symbols
src/createLogicMiddleware.js6 symbols
definitions/logic.d.ts6 symbols
test/createLogicMiddleware-latest.spec.js5 symbols
src/utils.js5 symbols

Dependencies from manifests, versioned

@babel/cli7.26.4 · 1×
@babel/core7.26.0 · 1×
@babel/preset-env7.26.0 · 1×
@babel/register7.25.9 · 1×
@types/mocha10.0.10 · 1×
@types/node20.9.0 · 1×
ajv8.17.1 · 1×
babel-loader9.2.1 · 1×
babel-plugin-istanbul6.1.1 · 1×
browserslist4.24.4 · 1×
core-js3.40.0 · 1×
cross-env7.0.3 · 1×

For agents

$ claude mcp add redux-logic \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact