README
Redux-builder
Generate your reducers, actions and manage your data, status and side effects easily ! This library aims to provide Redux / Redux-Saga capatibilities for highly-scalable app with reduced code. I'm a Typescript library I use it with React, but feel free to use it with anything if the need arise !
(How to use below)
Ideas behind redux-builder
I've been using redux for a long time now, always the same way. 3 files : Actions / Reducer / Saga. In my apps, reducers are linked to one external service, usually making a request. Each reducer has three parameters :
- status : Used to understand what state the service request is at
- error: Used to display an error, usually a string for me (Which I then translate in my app)
- some data, which is saved from the service result.
I have 4 status :
- "Start": Base status for my reducer
- "Loading": When the service has been called but did not respond yet
- "Failure": When the service returned an error
- "Success": When the service returned expect response
And 4 actions :
- Start : Called with the expected parameters to call the service, change the reducer's status to "Loading", captured by redux-saga to then make the call.
- Failure : Called when the the service throw an error, set an error inside the reducer.
- Success : Called by redux-saga to set some expected data inside the reducer.
- Reset : Called to put the whole reducer state back to its initial state.
This structure works very well for me, allow me to avoid any side-effect / complexity I had previously. The only downside is the amount of code needed on the redux-side and its repetitiveness.
One method to rule them all
redux-builder export a main function in order to build a reducer with its linked actions and saga.
import generateRedux from '@morintd/redux-builder';
This function is generic and takes 3-4 type variables :
- "StartDataType" : let's say your reducer aim to fetch an article by slug, this type would be something like
type StartDataType = {
slug: string;
}
- SuccessDataType : The type that is returned by your service, something like :
type SuccessDataType = {
article: {
title: string;
slug: string;
content: string
}
}
- ErrorDataType, mine is always a string but feel free to use any other one.
- StoredDataType, this one is optional. By default the module will expect a SuccessDataType but you can store something else.
If you only declared Start/Success DataType and want to store a string as error, you have the following :
import generateRedux from '@morintd/redux-builder';
generateRedux<StartDataType, SuccessDataType, string>
Or, if you use a StoredDataType
import generateRedux from '@morintd/redux-builder';
generateRedux<StartDataType, SuccessDataType, string, StoredDataType>
Now let's have a look at what arguments we can pass to this function.
- First is a string which will be used to build actions types, if my reducer aims to load an article by slug, it would be something like "FETCH_ARTICLE_BY_SLUG"
- Then, it takes an object that will be the initial data, something that'll match the given data to store. For my article, it would be :
article: {
slug: "",
title: "",
content: ""
}
- An initial error, matching ErrorDataType
- Finally, it takes an object that's sagas for the current reducer. This object can posses four properties, each working as a worker for redux-saga :
- onStart: triggered when action "Start" is dispatched, receive a payload matching StartDataType and must return data matching "SuccessDataType". If this one throw an error, the inner saga will dispatch a failure action.
- onFailure: triggered when action "failure" is dispatched receive a payload matching "ErrorDataType", must not return anything
- onSuccess: triggered when action "success" is dispatched receive a payload matching "SuccessDataType", must return data matching "StoredDataType".
- onReset, triggered when action "reset" is dispatched, receive and return nothing.
Putting everything together / How to use
The function generateRedux return an object with 4 properties : reducer, saga, workers, and actions.
- "workers" is a property used for mainly for testing purpose containing every saga workers.
- "reducer" can be used as just any other reducer
- "saga" contains a main worker, its defined in order to intercept (with takeLatest) corresponding action in order to call the right worker.
- "actions" : contains a list with actions for the generated reducer, always in the same order :
- action "start"
- action "success"
- action "error"
- action "reset"
const { reducer, workers, actions } = generateRedux<StartDataType, SuccessDataType, string>(
'FETCH_ARTICLE_BY_SLUG', // Base action
article: { // Initial data
slug: "",
title: "",
content: ""
},
'', // Initial error
{ // Workers
onStart: function*(payload: StartDataType) {
const request = (slug: string) =>
Promise.resolve(
[{ title: 'title', slug: 'slug', content: 'content' }].filter(
article => article.slug === slug
)
)
const articles = yield call(request, payload.slug)
return { articles }
}
}
);
const [fetchArticle, fetchArticleSuccess, fetchArticlesFailure, fetchArticlesReset] = actions;
export { reducer, saga, fetchArticle, fetchArticleSuccess, fetchArticlesFailure, fetchArticlesReset };
This way, i can just import my reducer and saga to use it as any other ones, and my actions with meaningful names to be used.
Advanced use
I want to use the main "generateRedux" function but i don't need a StartDataType
You can use this function with void instead of StartDataType :
generateRedux<void, SuccessDataType, string>(...);
I want to use the main "generateRedux" function but i don't need to store anything
You can use "void" instead of StoredDataType and pass "undefined" as initialData :
generateRedux<StartDataType, SuccessDataType, string, void>('BASE_ACTION', undefined, ...);
I would like to generate a reducer with its action, without a complex status/error handling
Another useful function exported by redux-builder is generateSimpleReducer. It takes a type variable matching data that'll be store, and two parameters :
- A base action, used the same way as "generateRedux"
- an initial data, matching the variable type given.
It returns a reducer and its action as a list. Here's an example for a "localReducer" aiming to store a language :
import { generateSimpleReducer } from '@mrtd/redux-builder';
const [reducer, setLocale] = generateSimpleReducer<{ language: 'fr' | 'en' }>('SET_LOCALE', { language: 'en' });
export { reducer, setLocale };
The inner functions of this package can be used if you need them, see Documentation