MCPcopy Index your code
hub / github.com/afitiskin/redux-saga-routines

github.com/afitiskin/redux-saga-routines @3.2.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release 3.2.1 ↗ · + Follow
21 symbols 67 edges 17 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

redux-saga-routines

A smart action creator for Redux. Useful for any kind of async actions like fetching data. Also fully compatible with Redux Saga and Redux Form.

Why do I need this?

Reduce boilerplate from your source code when making requests to API or validating forms build on top of Redux Form.

Installation

yarn add redux-saga-routines

or

npm install --save redux-saga-routines

What is routine?

Routine is a smart action creator that encapsulates 5 action types and 5 action creators to make standard actions lifecycle easy-to-use:

TRIGGER -> REQUEST -> SUCCESS / FAILURE -> FULFILL

So, with redux-saga-routines you don't need to create all these action type constants and action creators manually, just use createRoutine:

import { createRoutine } from 'redux-saga-routines';

// creating routine
const routine = createRoutine('ACTION_TYPE_PREFIX');

'ACTION_TYPE_PREFIX' passed to createRoutine is a name of routine (and a prefix for all it's action types).

You can access all action types using TRIGGER, REQUEST, SUCCESS, FAILURE, FULFILL attributes of routine object:

console.log(routine.TRIGGER); 
// 'ACTION_TYPE_PREFIX/TRIGGER';

console.log(routine.REQUEST); 
// 'ACTION_TYPE_PREFIX/REQUEST';

console.log(routine.SUCCESS); 
// 'ACTION_TYPE_PREFIX/SUCCESS';

console.log(routine.FAILURE); 
// 'ACTION_TYPE_PREFIX/FAILURE';

console.log(routine.FULFILL); 
// 'ACTION_TYPE_PREFIX/FULFILL';

You also have 5 action creators: trigger, request, success, failure, fulfill:

console.log(routine.trigger(payload)); 
// { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload };

console.log(routine.request(payload)); 
// { type: 'ACTION_TYPE_PREFIX/REQUEST', payload };

console.log(routine.success(payload)); 
// { type: 'ACTION_TYPE_PREFIX/SUCCESS', payload };

console.log(routine.failure(payload)); 
// { type: 'ACTION_TYPE_PREFIX/FAILURE', payload };

console.log(routine.fulfill(payload)); 
// { type: 'ACTION_TYPE_PREFIX/FULFILL', payload };

Routine by itself is a trigger action creator function:

// following calls will give you the same result
console.log(routine(payload)); // { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload };
console.log(routine.trigger(payload)); // { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload };

Every routine's action creator is a Flux Standard Action

Payload and meta creators for routines

redux-saga-routines based on redux-actions, so createRoutine actually accepts 3 parameters: (actionTypePrefix, payloadCreator, metaCreator) => function.

Changing action payload with payloadCreator

You may pass a function as a second argument to createRoutine and it will be used as a payload creator:

const routine = createRoutine('PREFIX', (value) => value * 2);

console.log(routine.trigger(1)); 
// { type: 'PREFIX/TRIGGER', payload: 2 }

console.log(routine.request(2)); 
// { type: 'PREFIX/TRIGGER', payload: 4 }

console.log(routine.success(3)); 
// { type: 'PREFIX/TRIGGER', payload: 6 }

console.log(routine.failure(4)); 
// { type: 'PREFIX/TRIGGER', payload: 8 }

console.log(routine.fulfill(5));
// { type: 'PREFIX/TRIGGER', payload: 10 }
````

You may also pass object as a second argument to define unique payload creator for each action:

```javascript
const payloadCreator = {
  trigger: (payload) => ({ ...payload, trigger: true }), // we may use payload creator to extend payload
  request: ({ id }) => ({ id }), // or to filter its values
  success: (payload) => ({ ...payload, data: parseData(payload.data) }), // or to change payload on the fly 
  failure: (payload) => ({ errorMessage: parseError(payload.error), error: true }), // or to do all of these at once
  fulfill: () => ({}), // or to completely change/remove payload ...
};

const routine = createRoutine('PREFIX', payloadCreator); // passing object as a second parameter

console.log(routine.trigger({ id: 42 })); 
// { type: 'PREFIX/TRIGGER', payload: { id: 42, trigger: true } }

console.log(routine.request({ id: 42, foo: 'bar' })); 
// { type: 'PREFIX/TRIGGER', payload: { id: 42 } }

console.log(routine.success({ id: 42, data: 'something' })); 
// { type: 'PREFIX/TRIGGER', payload: { id: 42, data: parseData('something') } }

console.log(routine.failure({ id: 42, error: 'oops...' })); 
// { type: 'PREFIX/TRIGGER', payload: { error: true, errorMessage: parseError('oops..') } }

console.log(routine.fulfill({ id: 42, foo: 'bar', baz: 'zab' })); 
// { type: 'PREFIX/TRIGGER', payload: {}} }

You may use lower or uppercase for payloadCreator-object keys:

const payloadCreator = {
  trigger: () => {}, // lowercase is okay
  REQUEST: () => {}, // uppercase is okay as well
};

Adding or changing action meta with metaCreator

createRoutine accept third parameter and treat it as metaCreator. It works almost the same as payloadCreator (function or object is accepted) the only difference is it works with action.meta instead of action.payload parameter:

const simpleMetaCreator = () => ({ foo: 'bar' });
const routineWithSimpleMeta = createRoutine('PREFIX', null, simpleMetaCreator);

console.log(routineWithSimpleMeta.trigger()); 
// { type: 'PREFIX/TRIGGER', payload: {}, meta: { foo: 'bar' } }

const complexMetaCreator = {
  trigger: () => ({ trigger: true }),
  request: () => ({ ignoreCache: true }),
  success: () => ({ saveToCache: true }),
  failure: () => ({ logSomewhere: true }),
  fulfill: () => ({ yo: 'bro!' }),
};

const routineWithComplexMeta =  createRoutine('PREFIX', null, complexMetaCreator);
console.log(routineWithSimpleMeta.trigger()); 
// { type: 'PREFIX/TRIGGER', payload: {}, meta: { trigger: true } }

console.log(routineWithSimpleMeta.request()); 
// { type: 'PREFIX/TRIGGER', payload: {}, meta: { ignoreCache: true } }

console.log(routineWithSimpleMeta.success()); 
// { type: 'PREFIX/TRIGGER', payload: {}, meta: { saveToCache: true } }

console.log(routineWithSimpleMeta.failure()); 
// { type: 'PREFIX/TRIGGER', payload: {}, meta: { logSomewhere: true } }

console.log(routineWithSimpleMeta.fulfill()); 
// { type: 'PREFIX/TRIGGER', payload: {}, meta: { yo: 'bro!' } }

Creating your own routines

Sometimes you may need custom routines, so now you are able to create your own routine creator to get them!

import { createRoutineCreator } from 'redux-saga-routines';

const createToggleRoutine = createRoutineCreator(['SHOW', 'HIDE', 'TOGGLE']);
console.log(createToggleRoutine.STAGES);
// ['SHOW', 'HIDE', 'TOGGLE']

const myToggler = createToggleRoutine('PREFIX'/*, payloadCreator, metaCreator*/);
console.log(myToggler._STAGES);
// ['SHOW', 'HIDE', 'TOGGLE']

console.log(myToggler._PREFIX);
// 'PREFIX'

console.log(myToggler.SHOW);
// 'PREFIX/SHOW'

console.log(myToggler.HIDE);
// 'PREFIX/HIDE'

console.log(myToggler.TOGGLE);
// 'PREFIX/TOGGLE'

console.log(myToggler.show(payload));
// { type: 'PREFIX/SHOW', payload }

console.log(myToggler.hide(payload));
// { type: 'PREFIX/HIDE', payload }

console.log(myToggler.toggle(payload));
// { type: 'PREFIX/TOGGLE', payload } 

So, now you are able to group any actions into custom routine and use it as you want!

createRoutineCreator also accepts custom separator as a second parameter, so you are able to change slash / with anything you want:

import { createRoutineCreator, routineStages } from 'redux-saga-routines';

const createUnderscoreRoutine = createRoutineCreator(routineStages, '_');
const routine = createUnderscoreRoutine('ACTION_TYPE_PREFIX');

console.log(routine.TRIGGER); 
// 'ACTION_TYPE_PREFIX_TRIGGER';

console.log(routine.REQUEST); 
// 'ACTION_TYPE_PREFIX_REQUEST';

console.log(routine.SUCCESS); 
// 'ACTION_TYPE_PREFIX_SUCCESS';

console.log(routine.FAILURE); 
// 'ACTION_TYPE_PREFIX_FAILURE';

console.log(routine.FULFILL); 
// 'ACTION_TYPE_PREFIX_FULFILL';

In example above you may notice, that default routine stages are also exported from the package, so you may use them to create your own extended routine. Now all the power is in your hands, use it as you want!

Usage examples

Example: fetching data from server

Let's start with creating routine for fetching some data from server:

// routines.js

import { createRoutine } from 'redux-saga-routines';
export const fetchData = createRoutine('FETCH_DATA');

Then, let's create some component, that triggers data fetching:

// FetchButton.js

import { connect } from 'react-redux';
import { fetchData } from './routines'; // import our routine

class FetchButton extends React.Component {
  static mapStateToProps = (state) => {
    return {...}; // map some state to component props
  }
  static mapDispatchToProps = {
    fetchData,
  };

  onClick() {
    this.props.fetchData(); // dispatching routine trigger action
  }

  render() {
    return (
      <button onClick={() => this.onClick()}>
        Fetch data from server
      </button>
    );
  }
}

export default connect(FetchButton.mapStateToProps, FetchButton.mapDispatchToProps)(FetchButton);

Now, let's take a look at reducer example:

// reducer.js

import { fetchData } from './routines';

const initialState = {
  data: null,
  loading: false,
  error: null,
};

export default function exampleReducer(state = initialState, action) {
  switch (action.type) {
    case fetchData.TRIGGER:
      return {
        ...state,
        loading: true,
      };
    case fetchData.SUCCESS:
      return {
        ...state,
        data: action.payload,
      };
    case fetchData.FAILURE:
      return {
        ...state,
        error: action.payload,
      };
    case fetchData.FULFILL:
      return {
        ...state,
        loading: false,
      };
    default:
      return state;
  }
}

And, saga (but you can use any other middleware, like redux-thunk):

// saga.js

import { fetchData } from './routines';

function* requestWatcherSaga() {
  // run fetchDataFromServer on every trigger action
  yield takeEvery(fetchData.TRIGGER, fetchDataFromServer)
}

function* fetchDataFromServer() {
  try {
    // trigger request action
    yield put(fetchData.request());
    // perform request to '/some_url' to fetch some data
    const response = yield call(apiClient.request, '/some_url');
    // if request successfully finished
    yield put(fetchData.success(response.data));
  } catch (error) {
    // if request failed
    yield put(fetchData.failure(error.message));
  } finally {
    // trigger fulfill action
    yield put(fetchData.fulfill());
  }
}

Filtering actions

It is a common case to ignore some triggered actions and not to perform API request every time. For example, let's make a saga, that perform API request only on odd button clicks (1st, 3rd, 5th, ...):

// saga.js

import { fetchData } from './routines';

function* requestWatcherSaga() {
  // run handleTriggerAction on every trigger action
  yield takeEvery(fetchData.TRIGGER, handleTriggerAction)
}

let counter = 0;
function* handleTriggerAction() {
  if (counter++ % 2 === 0) {
    // perform API request only on odd calls
    yield call(fetchDataFromServer);
  }

  // trigger fulfill action to finish routine lifecycle on every click
  yield put(fetchData.fulfill());
}

function* fetchDataFromServer() {
  try {
    // trigger request action
    yield put(fetchData.request());
    // perform request to '/some_url' to fetch some data
    const response = yield call(apiClient.request, '/some_url');
    // if request successfully finished
    yield put(fetchData.success(response.data));
  } catch (error) {
    // if request failed
    yield put(fetchData.failure(error.message));
  }
}

Wrap routine into promise

Sometimes it is useful to use promises (especially with 3rd-party components). With redux-saga-routines you are able to wrap your routine into promise and handle it in your saga! To achive this just add routinePromiseWatcherSaga in your sagaMiddleware.run(), for example like this:

import { routinePromiseWatcherSaga } from 'redux-saga-routines';

const sagas = [
  yourFirstSaga,
  yourOtherSaga,
  // ...,
  routinePromiseWatcherSaga,
];
sagas.forEach((saga) => sagaMiddleware.run(saga));

Now we are ready. There is special promisifyRoutine helper, that wraps your routine in function with signature: (payload, dispatch) => Promise. See example below: First, create routine:

// routines.js

import { createRoutine, promisifyRoutine } from 'redux-saga-routines';
export const myRoutine = createRoutine('MY_ROUTINE');
export const myRoutinePromiseCreator = promisifyRoutine(myRoutine);

Then, use it in your form component: ```javascript // MyComponent.js import { bindPromiseCreators } from 'redux-saga-routines'; import { myRoutine, myRoutinePromiseCreator } from './routines';

// since promise creator signature is (values, dispatch) => Promise // we have to bind it to dispatch using special helper bindPromiseCreator

class MyComponent extends React.Component { static mapStateToProps(state) { // return props object from selected from state }

static mapDispatchToProps(dispatch) { return { ...bindPromiseCreators({ myRoutinePromiseCreator, // other promise creators can be here... }, dispatch),

  // here you can use bindActionCreators from redux
  // to bind simple action creators
  // ...bindActionCreators({ mySimpleAction1, mySimpleAction2 }, dispatch)

  // or other helpers to bind other functions to store's dispatch
  // ...

  // or just pass dispatc

Core symbols most depended-on inside this repo

Shape

Function 21

Languages

TypeScript100%

Modules by API surface

src/utils.js5 symbols
src/routinePromiseWatcherSaga.js3 symbols
src/createRoutineCreator.js3 symbols
test/routinePromiseWatcherSaga.js2 symbols
test/bindPromiseCreators.js2 symbols
src/bindPromiseCreators.js2 symbols
test/promisifyRoutine.js1 symbols
test/bindRoutineToReduxForm.js1 symbols
src/promisifyRoutine.js1 symbols
src/bindRoutineToReduxForm.js1 symbols

For agents

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

⬇ download graph artifact

Ask about this repo answers extend the page