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.
Version 2
Module was totally reworked since version 2.0.0. If you still using version 1.* see version 1 docs
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
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
:
; // creating routineconst routine = ;
'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:
routineTRIGGER === 'ACTION_TYPE_PREFIX/TRIGGER';routineREQUEST === 'ACTION_TYPE_PREFIX/REQUEST';routineSUCCESS === 'ACTION_TYPE_PREFIX/SUCCESS';routineFAILURE === 'ACTION_TYPE_PREFIX/FAILURE';routineFULFILL === 'ACTION_TYPE_PREFIX/FULFILL';
You also have 5 action creators: trigger
, request
, success
, failure
, fulfill
:
routine === type: 'ACTION_TYPE_PREFIX/TRIGGER' payload ;routine === type: 'ACTION_TYPE_PREFIX/REQUEST' payload ;routine === type: 'ACTION_TYPE_PREFIX/SUCCESS' payload ;routine === type: 'ACTION_TYPE_PREFIX/FAILURE' payload ;routine === type: 'ACTION_TYPE_PREFIX/FULFILL' payload ;
Routine by itself is a trigger action creator function:
todeep;
redux-saga-routines
based on redux-actions, so createRoutine
actually accepts 3 parameters: (actionTypePrefix, payloadCreator, metaCreator) => function
.
Every routine action creator is a redux-actions
FSA, so you can use them with handleAction(s)
or combineActions
from redux-actions
Usage
Example: fetching data from server
Let's start with creating routine for fetching some data from server:
// routines.js ;const fetchData = ;
Then, let's create some component, that triggers data fetching:
// FetchButton.js ;; // import our routine Component static { return ...; // map some state to component props } static mapDispatchToProps = fetchData ; { thisprops; // dispatching routine trigger action } { return <button onClick= this> Fetch data from server </button> ; } FetchButtonmapStateToProps FetchButtonmapDispatchToPropsFetchButton;
Now, let's take a look at reducer example:
// reducer.js ; const initialState = data: null loading: false error: null; { }
And, saga (but you can use any other middleware, like redux-thunk
):
// saga.js ; { // run fetchDataFromServer on every trigger action } { try // trigger request action ; // perform request to '/some_url' to fetch some data const response = ; // if request successfully finished ; catch error // if request failed ; finally // trigger fulfill action ; }
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 ; { // run handleTriggerAction on every trigger action } let counter = 0; { if counter++ % 2 === 0 // perform API request only on odd calls ; // trigger fulfill action to finish routine lifecycle on every click ;} { try // trigger request action ; // perform request to '/some_url' to fetch some data const response = ; // if request successfully finished ; catch error // if request failed ; }
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:
; const sagas = yourFirstSaga yourOtherSaga // ..., routinePromiseWatcherSaga;sagas;
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 ;const myRoutine = ;const myRoutinePromiseCreator = ;
Then, use it in your form component:
// MyComponent.js;; // since promise creator signature is (values, dispatch) => Promise// we have to bind it to dispatch using special helper bindPromiseCreator Component static { // return props object from selected from state } static { return ... // 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 dispatch as a prop to component dispatch ; } { const promise = thisprops; // so, call of myRoutinePromiseCreator returns promise // you can use this promise as you want promise; // internally when you call myRoutinePromiseCreator() special action with type ROUTINE_PROMISE_ACTION is dispatched // this special action is handled by routinePromiseWatcherSaga // to resolve promise you need to dispatch myRoutine.success(successPayload) action, successPayload will be passed to resolved promise // if myRoutine.failure(failurePayload) is dispatched, promise will be rejected with failurePayload. // we just want to wait 5 seconds and then resolve promise with 'voila!' message: ; // same example, but with promise rejection: // setTimeout( // () => this.props.dispatch(myRoutine.failure('something went wrong...')), // 5000, // ); // of course you don't have to do it in your component // you can do it in your saga // see below } { return <button onClick= this> /* your form fields here... */ </form> ; } MyComponentmapStateToProps MyComponentmapDispatchToPropsMyComponent;
You are able to resolve/reject given promise in your saga:
// saga.js; { // when you call myRoutinePromiseCreator(somePayload) // internally myRoutine.trigger(somePayload) action is dispatched // we take every routine trigger actions and handle them } { const payload = action; // here payload is somePayload passed from myRoutinePromiseCreator(somePayload) const isDataCorrect = ; if isDataCorrect // send data to server ; else // reject given promise ; // trigger fulfill action to end routine lifecycle ;} { try // trigger request action ; // perform request to '/endpoint' const response = ; // if request successfully finished we resolve promise with response data ; catch error // if request failed we reject promise with error message
redux-saga
, redux-form
, redux-saga-routines
combo
You 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 are 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
:
// routines.js ;const submitFormRoutine = ;const submitFormHandler = ;
Then, use it in your form component:
// MyForm.js ;; // you do not need to bind your handler to store, since `redux-form` pass `dispatch` to handler. Component { return <form onSubmit=thisprops> /* your form fields here... */ </form> ; } MyForm;
Now you are able to handle form submission in your saga:
// saga.js;; { // run validation on every trigger action } { // redux-form pass form values and component props to submit handler // so they passed to trigger action as an action payload const values props = actionpayload; if ! // client-side validation failed const errors = ; // reject promise given to redux-form, pass errors as SubmissionError object according to redux-form docs ; else // send form data to server ; // trigger fulfill action to end routine lifecycle ;} { try // trigger request action ; // perform request to '/submit' to send form data const response = ; // if request successfully finished ; catch error // if request failed ; }
License
MIT