UNPKG

redux-dispatcher

Version:

All-in-one simple solution to manage actions with less code

298 lines (235 loc) 8.4 kB
[![](https://badge.fury.io/js/redux-dispatcher.svg)](https://badge.fury.io/js/redux-dispatcher) ![](https://img.shields.io/npm/dm/redux-dispatcher) ![](https://img.shields.io/npms-io/maintenance-score/redux-dispatcher) ![](https://img.shields.io/npm/types/redux-dispatcher) ## redux-dispatcher is an all-in-one simple solution to manage actions with less code 🦄 **Its main purpose is to combine action type, action creator and dispatch function into one, then you no need to worry about defining and managing action type constants.** ## Short example ```js import { createDispatcher } from 'redux-dispatcher' const key = "app" const mapDispatch = { setUser: name => ({ name }) }; const appDispatcher = createDispatcher(key, mapDispatch) ``` ```js appDispatcher.setUser("Anonymous") // dispatch action {type: "app/SET_USER", name: "Anonymous"} ``` **Guaranteed**: - Intuitive - Less code **Suitable**: - If you use Redux and want to reduce boilerplate - If you find it tiresome every time you need to define, import, manage actions **Advanced functionalities**: - [Action with side effect](#user-content-retrieve-side-effect-result-after-dispatching-an-action-no-more-callback) - [Thunk support](#user-content-define-thunk-like-your-favourite-redux-thunk) - [Immutable helpers](#user-content-immutable-helper-for-state) ## Usage ### 1. Install and setup ```npm install redux-dispatcher --save``` **Setup** ```js import {applyMiddleware, createStore} from "redux"; import {dispatcherMiddleware} from "redux-dispatcher"; const store = createStore( reducer, applyMiddleware(dispatcherMiddleware) ); ``` ### 2. Define action type, action creator and dispatch action With **redux-dispatcher**, the action type is implicitly computed according to the ```key``` passed to ```createDispatcher``` method and the name of the action creator. But if you want to explicitly specify a type, you can include a ```type``` property in the return object. ```js import { createDispatcher } from 'redux-dispatcher'; const key = "profile"; const mapDispatchToAC = { // type = "profile/FETCH_PROFILE" // if the action doesn't depend on parameters, you can just write a plain object fetchProfile: {loading: true}, // if not explicitly specified, the action type will be automatically set: type = "profile/UPDATE_PROFILE" updateProfile: (username, password) => ({ // type: "UPDATE_PROFILE", you can specify the action type here username, password }) }; const profileDispatcher = createDispatcher(key, mapDispatchToAC); ``` To dispatch action, you can just import the dispatcher you need and dispatch action anywhere you want ```js profileDispatcher.updateProfile("my_username", "my_password"); ``` ### 3. Handle action in reducer Create ```reducer``` with **redux-dispatcher** is as easy as create a usual ```reducer```, with less code. ```js import { createReducer } from 'redux-dispatcher'; const mapActionToReducer = () => ({ // similar to fall-through case in switch statement [[ profileDispatcher.fetchProfile, profileDispatcher.reloadProfile, profileDispatcher.resetProfile ]]: (state, payload) => payload, // notice the payload, it doesn't have "type" property like action [profileDispatcher.loadingProfile]: {loading: true}, // you can just write a plain object if new state doesn't computed from current state or action payload [profileDispatcher.updateProfile]: (state, {username, password}) => ({ username, password: encrypt(password) }) // only return what data need to be merged in state // the default case is handled automatically }) const profileReducer = createReducer(initialState, mapActionToReducer); const rootReducer = combineReducers({ profile: profileReducer, }); ``` ### 4. Advanced functionalities This section describes some useful features and extensions you may find interesting like thunk and immutable helper. #### Retrieve side effect result after dispatching an action <details> <summary> When your action trigger some side effect (like fetching API), you can use built-in hooks <b>dispatchResult</b> or <b>waitResult</b> to dispatch and subscribe for results from action. (Available from v1.9.6) </summary> [See example](https://github.com/blueish9/redux-dispatcher/example/enhanceAction.js). Use case with React: ```js const mapDispatchToAC = { fetchProfile: userId => ({ userId }), }; const userDispatcher = createDispatcher('user', mapDispatchToAC); ``` ```js // Component A async componentDidMount() { const action = userDispatcher.fetchProfile(userId) const profile = await action.waitResult() // profile = { name: "Emily" } } ``` In your side effect handler (example with [Redux Saga](https://redux-saga.js.org)): ```js import { take } from 'redux-saga/effects' function* fetchProfile(action) { const profile = { name: "Emily" } // call your side effect here (like API request) action.dispatchResult(profile) } function* sagaWatcher() { yield take(userDispatcher.fetchProfile, fetchProfile) } ``` If you want to subscribe for result from other places: ```js // Component A calls userDispatcher.fetchProfile // but Component B and Component C also want to subscribe for the action's result import { waitResult } from "redux-dispatcher"; // Component B async componentDidMount() { // this Promise will be resolved when dispatchResult is called. // if dispatchResult has already been called before, this waitResult will immediately return a cached result const result = await waitResult(userDispatcher.fetchProfile) } // Component C componentDidMount() { const unsubscribe = waitResult(userDispatcher.fetchProfile, result => { // each time dispatchResult is called, this callback will be triggered }) // to remove the callback from listening to result, simply call unsubscribe() } // in Component A, you can also subscribe for continuous results like in Component C componentDidMount() { userDispatcher.fetchProfile(userId).waitResult(result => { // each time dispatchResult is called, this callback will be triggered }) } ``` </details> --- #### Define thunk like your favourite [Redux Thunk](https://github.com/reduxjs/redux-thunk) <details> <summary> See example </summary> ```js const mapDispatchToAC = { fetchUser: id => ({dispatch, getState, context}) => { // do something } } ``` You can also provide global context to `dispatcherMiddleware` just like how Redux Thunk middleware **inject** custom arguments, [read more](https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument). ```js import {dispatcherMiddleware} from "redux-dispatcher" const context = { BASE_API_URL, FetchHelper } const store = createStore( reducer, applyMiddleware(dispatcherMiddleware.withContext(context)) ) // reducer const mapActionToReducer = context => { } ``` </details> --- #### Immutable helpers for state <details> <summary> See example </summary> ```js const profileReducer = createReducer(initialState, { /* equivalent to: case "profile/UPDATE_STREET": return { ...state, userInfo: { ...state.userInfo, address: { ...state.userInfo.address, street: action.street } } } */ [profileDispatcher.updateStreet]: (state, {street}, {set}) => ({ street: set('userInfo.address.street', street) }) }); ``` All immutable helper functions are based on [dot-prop-immutable](https://github.com/debitoor/dot-prop-immutable) ```js [profileDispatcher.updateStreet]: (state, payload, {get, set, merge, toggle, remove}) => ({ }) ``` </details> --- #### Easily manage action types <details> <summary> See example </summary> ```js profileDispatcher.key === "profile" // true profileDispatcher.updateProfile.type === "profile/UPDATE_PROFILE" // true /* equivalent to: const handler = { "profile/UPDATE_PROFILE": (state, payload) => {} } */ const handler = { [profileDispatcher.updateProfile]: (state, payload) => {} } ``` An example when working with [Redux Saga](https://redux-saga.js.org): Instead of passing an action type, you can just pass a dispatcher function to the ```takeLatest``` function. ```js const action = yield take(profileDispatcher.updateProfile) // action = { type, username, password } ``` </details>