Gathering detailed insights and metrics for redux-saga-routines
Gathering detailed insights and metrics for redux-saga-routines
Gathering detailed insights and metrics for redux-saga-routines
Gathering detailed insights and metrics for redux-saga-routines
@types/redux-saga-routines
TypeScript definitions for redux-saga-routines
@darcusfenix/redux-saga-routines
Routines for redux-saga also useful with redux-form
extend-saga-routines
Extend any routine with custom stages, create routine that has redux-saga-routines default stages and create routine with only yours custom stages.
redux-saga-routines-for-v100beta0
Routines for redux-saga also useful with redux-form. Temporary package until original package has added support for redux-saga > 1.0.0.
npm install redux-saga-routines
Typescript
Module System
Node Version
NPM Version
75.5
Supply Chain
86.8
Quality
74.9
Maintenance
100
Vulnerability
100
License
JavaScript (100%)
Total Downloads
3,191,298
Last Day
557
Last Week
6,729
Last Month
38,340
Last Year
493,578
249 Stars
84 Commits
33 Forks
8 Watching
15 Branches
13 Contributors
Minified
Minified + Gzipped
Latest Version
3.2.3
Package Id
redux-saga-routines@3.2.3
Size
38.61 kB
NPM Version
6.14.4
Node Version
13.12.0
Publised On
12 May 2020
Cumulative downloads
Total Downloads
Last day
-66.6%
557
Compared to previous day
Last week
-31.2%
6,729
Compared to previous week
Last month
-9.7%
38,340
Compared to previous month
Last year
-9.8%
493,578
Compared to previous year
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.
Reduce boilerplate from your source code when making requests to API or validating forms build on top of Redux Form.
1yarn add redux-saga-routines
or
1npm install --save redux-saga-routines
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
:
1import { createRoutine } from 'redux-saga-routines'; 2 3// creating routine 4const 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:
1console.log(routine.TRIGGER); 2// 'ACTION_TYPE_PREFIX/TRIGGER'; 3 4console.log(routine.REQUEST); 5// 'ACTION_TYPE_PREFIX/REQUEST'; 6 7console.log(routine.SUCCESS); 8// 'ACTION_TYPE_PREFIX/SUCCESS'; 9 10console.log(routine.FAILURE); 11// 'ACTION_TYPE_PREFIX/FAILURE'; 12 13console.log(routine.FULFILL); 14// 'ACTION_TYPE_PREFIX/FULFILL';
You also have 5 action creators: trigger
, request
, success
, failure
, fulfill
:
1console.log(routine.trigger(payload)); 2// { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload }; 3 4console.log(routine.request(payload)); 5// { type: 'ACTION_TYPE_PREFIX/REQUEST', payload }; 6 7console.log(routine.success(payload)); 8// { type: 'ACTION_TYPE_PREFIX/SUCCESS', payload }; 9 10console.log(routine.failure(payload)); 11// { type: 'ACTION_TYPE_PREFIX/FAILURE', payload }; 12 13console.log(routine.fulfill(payload)); 14// { type: 'ACTION_TYPE_PREFIX/FULFILL', payload };
Routine by itself is a trigger action creator function:
1// following calls will give you the same result 2console.log(routine(payload)); // { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload }; 3console.log(routine.trigger(payload)); // { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload };
Every routine's action creator is a Flux Standard Action
redux-saga-routines
based on redux-actions, so createRoutine
actually accepts 3 parameters: (actionTypePrefix, payloadCreator, metaCreator) => function
.
payloadCreator
You may pass a function as a second argument to createRoutine
and it will be used as a payload creator:
1const routine = createRoutine('PREFIX', (value) => value * 2); 2 3console.log(routine.trigger(1)); 4// { type: 'PREFIX/TRIGGER', payload: 2 } 5 6console.log(routine.request(2)); 7// { type: 'PREFIX/TRIGGER', payload: 4 } 8 9console.log(routine.success(3)); 10// { type: 'PREFIX/TRIGGER', payload: 6 } 11 12console.log(routine.failure(4)); 13// { type: 'PREFIX/TRIGGER', payload: 8 } 14 15console.log(routine.fulfill(5)); 16// { type: 'PREFIX/TRIGGER', payload: 10 }
You may also pass object as a second argument to define unique payload creator for each action:
1const payloadCreator = { 2 trigger: (payload) => ({ ...payload, trigger: true }), // we may use payload creator to extend payload 3 request: ({ id }) => ({ id }), // or to filter its values 4 success: (payload) => ({ ...payload, data: parseData(payload.data) }), // or to change payload on the fly 5 failure: (payload) => ({ errorMessage: parseError(payload.error), error: true }), // or to do all of these at once 6 fulfill: () => ({}), // or to completely change/remove payload ... 7}; 8 9const routine = createRoutine('PREFIX', payloadCreator); // passing object as a second parameter 10 11console.log(routine.trigger({ id: 42 })); 12// { type: 'PREFIX/TRIGGER', payload: { id: 42, trigger: true } } 13 14console.log(routine.request({ id: 42, foo: 'bar' })); 15// { type: 'PREFIX/TRIGGER', payload: { id: 42 } } 16 17console.log(routine.success({ id: 42, data: 'something' })); 18// { type: 'PREFIX/TRIGGER', payload: { id: 42, data: parseData('something') } } 19 20console.log(routine.failure({ id: 42, error: 'oops...' })); 21// { type: 'PREFIX/TRIGGER', payload: { error: true, errorMessage: parseError('oops..') } } 22 23console.log(routine.fulfill({ id: 42, foo: 'bar', baz: 'zab' })); 24// { type: 'PREFIX/TRIGGER', payload: {}} }
You may use lower or uppercase for payloadCreator
-object keys:
1const payloadCreator = { 2 trigger: () => {}, // lowercase is okay 3 REQUEST: () => {}, // uppercase is okay as well 4};
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:
1const simpleMetaCreator = () => ({ foo: 'bar' }); 2const routineWithSimpleMeta = createRoutine('PREFIX', null, simpleMetaCreator); 3 4console.log(routineWithSimpleMeta.trigger()); 5// { type: 'PREFIX/TRIGGER', payload: {}, meta: { foo: 'bar' } } 6 7const complexMetaCreator = { 8 trigger: () => ({ trigger: true }), 9 request: () => ({ ignoreCache: true }), 10 success: () => ({ saveToCache: true }), 11 failure: () => ({ logSomewhere: true }), 12 fulfill: () => ({ yo: 'bro!' }), 13}; 14 15const routineWithComplexMeta = createRoutine('PREFIX', null, complexMetaCreator); 16console.log(routineWithSimpleMeta.trigger()); 17// { type: 'PREFIX/TRIGGER', payload: {}, meta: { trigger: true } } 18 19console.log(routineWithSimpleMeta.request()); 20// { type: 'PREFIX/TRIGGER', payload: {}, meta: { ignoreCache: true } } 21 22console.log(routineWithSimpleMeta.success()); 23// { type: 'PREFIX/TRIGGER', payload: {}, meta: { saveToCache: true } } 24 25console.log(routineWithSimpleMeta.failure()); 26// { type: 'PREFIX/TRIGGER', payload: {}, meta: { logSomewhere: true } } 27 28console.log(routineWithSimpleMeta.fulfill()); 29// { type: 'PREFIX/TRIGGER', payload: {}, meta: { yo: 'bro!' } }
Sometimes you may need custom routines, so now you are able to create your own routine creator to get them!
1import { createRoutineCreator } from 'redux-saga-routines'; 2 3const createToggleRoutine = createRoutineCreator(['SHOW', 'HIDE', 'TOGGLE']); 4console.log(createToggleRoutine.STAGES); 5// ['SHOW', 'HIDE', 'TOGGLE'] 6 7const myToggler = createToggleRoutine('PREFIX'/*, payloadCreator, metaCreator*/); 8console.log(myToggler._STAGES); 9// ['SHOW', 'HIDE', 'TOGGLE'] 10 11console.log(myToggler._PREFIX); 12// 'PREFIX' 13 14console.log(myToggler.SHOW); 15// 'PREFIX/SHOW' 16 17console.log(myToggler.HIDE); 18// 'PREFIX/HIDE' 19 20console.log(myToggler.TOGGLE); 21// 'PREFIX/TOGGLE' 22 23console.log(myToggler.show(payload)); 24// { type: 'PREFIX/SHOW', payload } 25 26console.log(myToggler.hide(payload)); 27// { type: 'PREFIX/HIDE', payload } 28 29console.log(myToggler.toggle(payload)); 30// { 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:
1import { createRoutineCreator, defaultRoutineStages } from 'redux-saga-routines'; 2 3const createUnderscoreRoutine = createRoutineCreator(defaultRoutineStages, '_'); 4const routine = createUnderscoreRoutine('ACTION_TYPE_PREFIX'); 5 6console.log(routine.TRIGGER); 7// 'ACTION_TYPE_PREFIX_TRIGGER'; 8 9console.log(routine.REQUEST); 10// 'ACTION_TYPE_PREFIX_REQUEST'; 11 12console.log(routine.SUCCESS); 13// 'ACTION_TYPE_PREFIX_SUCCESS'; 14 15console.log(routine.FAILURE); 16// 'ACTION_TYPE_PREFIX_FAILURE'; 17 18console.log(routine.FULFILL); 19// '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!
Let's start with creating routine for fetching some data from server:
1// routines.js 2 3import { createRoutine } from 'redux-saga-routines'; 4export const fetchData = createRoutine('FETCH_DATA');
Then, let's create some component, that triggers data fetching:
1// FetchButton.js 2 3import { connect } from 'react-redux'; 4import { fetchData } from './routines'; // import our routine 5 6class FetchButton extends React.Component { 7 static mapStateToProps = (state) => { 8 return {...}; // map some state to component props 9 } 10 static mapDispatchToProps = { 11 fetchData, 12 }; 13 14 onClick() { 15 this.props.fetchData(); // dispatching routine trigger action 16 } 17 18 render() { 19 return ( 20 <button onClick={() => this.onClick()}> 21 Fetch data from server 22 </button> 23 ); 24 } 25} 26 27export default connect(FetchButton.mapStateToProps, FetchButton.mapDispatchToProps)(FetchButton);
Now, let's take a look at reducer example:
1// reducer.js 2 3import { fetchData } from './routines'; 4 5const initialState = { 6 data: null, 7 loading: false, 8 error: null, 9}; 10 11export default function exampleReducer(state = initialState, action) { 12 switch (action.type) { 13 case fetchData.TRIGGER: 14 return { 15 ...state, 16 loading: true, 17 }; 18 case fetchData.SUCCESS: 19 return { 20 ...state, 21 data: action.payload, 22 }; 23 case fetchData.FAILURE: 24 return { 25 ...state, 26 error: action.payload, 27 }; 28 case fetchData.FULFILL: 29 return { 30 ...state, 31 loading: false, 32 }; 33 default: 34 return state; 35 } 36}
And, saga (but you can use any other middleware, like redux-thunk
):
1// saga.js 2 3import { fetchData } from './routines'; 4 5function* requestWatcherSaga() { 6 // run fetchDataFromServer on every trigger action 7 yield takeEvery(fetchData.TRIGGER, fetchDataFromServer) 8} 9 10function* fetchDataFromServer() { 11 try { 12 // trigger request action 13 yield put(fetchData.request()); 14 // perform request to '/some_url' to fetch some data 15 const response = yield call(apiClient.request, '/some_url'); 16 // if request successfully finished 17 yield put(fetchData.success(response.data)); 18 } catch (error) { 19 // if request failed 20 yield put(fetchData.failure(error.message)); 21 } finally { 22 // trigger fulfill action 23 yield put(fetchData.fulfill()); 24 } 25}
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, ...):
1// saga.js 2 3import { fetchData } from './routines'; 4 5function* requestWatcherSaga() { 6 // run handleTriggerAction on every trigger action 7 yield takeEvery(fetchData.TRIGGER, handleTriggerAction) 8} 9 10let counter = 0; 11function* handleTriggerAction() { 12 if (counter++ % 2 === 0) { 13 // perform API request only on odd calls 14 yield call(fetchDataFromServer); 15 } 16 17 // trigger fulfill action to finish routine lifecycle on every click 18 yield put(fetchData.fulfill()); 19} 20 21function* fetchDataFromServer() { 22 try { 23 // trigger request action 24 yield put(fetchData.request()); 25 // perform request to '/some_url' to fetch some data 26 const response = yield call(apiClient.request, '/some_url'); 27 // if request successfully finished 28 yield put(fetchData.success(response.data)); 29 } catch (error) { 30 // if request failed 31 yield put(fetchData.failure(error.message)); 32 } 33}
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:
1import { routinePromiseWatcherSaga } from 'redux-saga-routines'; 2 3const sagas = [ 4 yourFirstSaga, 5 yourOtherSaga, 6 // ..., 7 routinePromiseWatcherSaga, 8]; 9sagas.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:
1// routines.js 2 3import { createRoutine, promisifyRoutine } from 'redux-saga-routines'; 4export const myRoutine = createRoutine('MY_ROUTINE'); 5export const myRoutinePromiseCreator = promisifyRoutine(myRoutine);
Then, use it in your form component:
1// MyComponent.js 2import { bindPromiseCreators } from 'redux-saga-routines'; 3import { myRoutine, myRoutinePromiseCreator } from './routines'; 4 5// since promise creator signature is (values, dispatch) => Promise 6// we have to bind it to dispatch using special helper bindPromiseCreator 7 8class MyComponent extends React.Component { 9 static mapStateToProps(state) { 10 // return props object from selected from state 11 } 12 13 static mapDispatchToProps(dispatch) { 14 return { 15 ...bindPromiseCreators({ 16 myRoutinePromiseCreator, 17 // other promise creators can be here... 18 }, dispatch), 19 20 // here you can use bindActionCreators from redux 21 // to bind simple action creators 22 // ...bindActionCreators({ mySimpleAction1, mySimpleAction2 }, dispatch) 23 24 // or other helpers to bind other functions to store's dispatch 25 // ... 26 27 // or just pass dispatch as a prop to component 28 dispatch, 29 }; 30 } 31 32 33 handleClick() { 34 const promise = this.props.myRoutinePromiseCreator(somePayload); 35 // so, call of myRoutinePromiseCreator returns promise 36 // you can use this promise as you want 37 38 promise.then( 39 (successPayload) => console.log('success :)', successPayload), 40 (failurePayload) => console.log('failure :(', failurePayload), 41 ); 42 43 44 // internally when you call myRoutinePromiseCreator() special action with type ROUTINE_PROMISE_ACTION is dispatched 45 // this special action is handled by routinePromiseWatcherSaga 46 47 // to resolve promise you need to dispatch myRoutine.success(successPayload) action, successPayload will be passed to resolved promise 48 // if myRoutine.failure(failurePayload) is dispatched, promise will be rejected with failurePayload. 49 50 // we just want to wait 5 seconds and then resolve promise with 'voila!' message: 51 setTimeout( 52 () => this.props.dispatch(myRoutine.success('voila!')), 53 5000, 54 ); 55 56 // same example, but with promise rejection: 57 // setTimeout( 58 // () => this.props.dispatch(myRoutine.failure('something went wrong...')), 59 // 5000, 60 // ); 61 62 // of course you don't have to do it in your component 63 // you can do it in your saga 64 // see below 65 } 66 67 render() { 68 return ( 69 <button onClick={() => this.handleClick()}> 70 {/* your form fields here... */} 71 </form> 72 ); 73 } 74} 75 76export default connect(MyComponent.mapStateToProps, MyComponent.mapDispatchToProps)(MyComponent);
You are able to resolve/reject given promise in your saga:
1// saga.js 2import { myRoutine } from './routines'; 3 4function* myRoutineTriggerWatcher() { 5 // when you call myRoutinePromiseCreator(somePayload) 6 // internally myRoutine.trigger(somePayload) action is dispatched 7 // we take every routine trigger actions and handle them 8 yield takeEvery(myRoutine.TRIGGER, handleTriggerAction) 9} 10 11function* handleTriggerAction(action) { 12 const { payload } = action; // here payload is somePayload passed from myRoutinePromiseCreator(somePayload) 13 const isDataCorrect = verifyData(payload); 14 15 if (isDataCorrect) { 16 // send data to server 17 yield call(sendFormDataToServer, payload); 18 } else { 19 // reject given promise 20 yield put(myRoutine.failure('something went wrong')); 21 } 22 23 // trigger fulfill action to end routine lifecycle 24 yield put(myRoutine.fulfill()); 25} 26 27function* sendFormDataToServer(data) { 28 try { 29 // trigger request action 30 yield put(myRoutine.request()); 31 // perform request to '/endpoint' 32 const response = yield call(apiClient.request, '/endpoint', data); 33 // if request successfully finished we resolve promise with response data 34 yield put(myRoutine.success(response.data)); 35 } catch (error) { 36 // if request failed we reject promise with error message 37 yield put(myRoutine.failure(error.message); 38 } 39}
redux-saga
, redux-form
, redux-saga-routines
comboYou are also allowed to use combo of redux-saga
, redux-form
and redux-saga-routines
!
Since redux-form
validation based on promises, you are able to handle redux-form
validation in your saga.
To achive this just add routinePromiseWatcherSaga
in your sagaMiddleware.run()
, like in example above.
There is a special bindRoutineToReduxForm
helper, that wraps your routine in function with redux-form
compatible signature: (values, dispatch, props) => Promise
(it works just like promisifyRoutine
but more specific to be compatible with full redux-form
functionality)
First, create routine and it's wrapper for redux-form
:
1// routines.js 2 3import { createRoutine, bindRoutineToReduxForm } from 'redux-saga-routines'; 4export const submitFormRoutine = createRoutine('SUBMIT_MY_FORM'); 5export const submitFormHandler = bindRoutineToReduxForm(submitFormRoutine);
Then, use it in your form component:
1// MyForm.js 2 3import { reduxForm } from 'redux-form'; 4import { submitFormHandler } from './routines'; 5 6// you do not need to bind your handler to store, since `redux-form` pass `dispatch` to handler. 7 8class MyForm extends React.Component { 9 render() { 10 return ( 11 <form onSubmit={this.props.handleSubmit(submitFormHandler)}> 12 {/* your form fields here... */} 13 </form> 14 ); 15 } 16} 17 18export default reduxForm()(MyForm);
Now you are able to handle form submission in your saga:
1// saga.js 2import { SubmissionError } from 'redux-form'; 3import { submitFormRoutine } from './routines'; 4 5function* validateFormWatcherSaga() { 6 // run validation on every trigger action 7 yield takeEvery(submitFormRoutine.TRIGGER, validate) 8} 9 10function* validate(action) { 11 // redux-form pass form values and component props to submit handler 12 // so they passed to trigger action as an action payload 13 const { values, props } = action.payload; 14 15 if (!isValid(values, props)) { 16 // client-side validation failed 17 const errors = getFormErrors(values, props); 18 // reject promise given to redux-form, pass errors as SubmissionError object according to redux-form docs 19 yield put(submitFormRoutine.failure(new SubmissionError(errors))); 20 } else { 21 // send form data to server 22 yield call(sendFormDataToServer, values); 23 } 24 25 // trigger fulfill action to end routine lifecycle 26 yield put(submitFormRoutine.fulfill()); 27} 28 29function* sendFormDataToServer(formData) { 30 try { 31 // trigger request action 32 yield put(submitFormRoutine.request()); 33 // perform request to '/submit' to send form data 34 const response = yield call(apiClient.request, '/submit', formData); 35 // if request successfully finished 36 yield put(submitFormRoutine.success(response.data)); 37 } catch (error) { 38 // if request failed 39 yield put(submitFormRoutine.failure(new SubmissionError({ _error: error.message }))); 40 } 41}
Module was totally reworked since version 2.0.0. If you still using version 1.* see version 1 docs
MIT
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 8/26 approved changesets -- score normalized to 3
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
38 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-12-23
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More