use-reducer-effect
v1.1.2
Published
A tiny library that enables side effects with the useReducer hook
Downloads
263
Maintainers
Readme
Table of Contents
- The Problem
- The Solution
- Getting started
- Defining the effect function
- Data Flow
- API
- Advanced
- Examples
- Acknowledgments
- Contributors
The Problem
You are using the useReducer
hook to manage your state, but you need a way to perform side effects (like fetching data from an API or logging) without mixing those concern together.
The Solution
use-reducer-effect
let's you define a second function next to the reducer function, where you can manage your side effects.
This way you can run your side effect logic on certain actions, while also allowing side effects to update the store by dispatching new actions.
Getting started
Install the library with
npm install --save use-reducer-effect
and import the hook into your code
import { useReducerWithSideEffects } from "use-reducer-effect";
If you already use a reducer to manage your state you will feel right at home with using them to manage side effects to.
useReducerWithSideEffects
uses the same API as useReducer
does, but gives you the ability to define a function which is called after your reducer function has run to
run additional logic like fetching data from an API.
Defining the effect function
The effect function takes the same argument as the reducer function
.
state
and action
.
Instead of modifying the state, it is responsible for handling side effects like making http calls.
Let's assume we want to fetch from an API whenever an action called LOAD is dispatched. When the data is there, we want to feed it back into our reducer.
Note: The effect function will be called after the reducer has updated the state.
// define the reducer as usual
function reducer(state: State, action) {
switch (action.type) {
case ACTION_LOAD: // this action is dispatched by clicking on the button
return { ...state, loading: true };
case ACTION_LOAD_SUCCESSFUL: // this action is dispatched by the side effect. We'll learn how to do this in a minute.
return { ...state, loading: false, data: action.payload };
}
return state;
}
// define the effect function which is executed after the reducer has run
async function effect(state, action) {
switch (action.type) {
case ACTION_LOAD:
const response = await fetch(
`https://api.github.com/users/${action.payload}/repos`
);
const data = await response.json();
return {
type: ACTION_LOAD_SUCCESSFUL,
payload: data
};
}
}
// using the hook
const MyComponent = () => {
const [state, dispatch] = useReducerWithSideEffects(reducer, effect);
};
Data Flow
- The
action LOAD
is dispatched with a payload (in this case the GitHub username) - The
reducer function
is run and updated state is returned to the component - The
effect function
is called, which has access to the updated state and the dispatched action (LOAD) - The
effect function
triggers an http request - It dispatches the
action ACTION_LOAD_SUCCESSFUL
with the API response data as payload - The
reducer function
is called again (ACTION_LOAD_SUCCESSFUL) and updates the state - The
effect function
is called again with the new state (API response) and theaction ACTION_LOAD_SUCCESSFUL
. We didn't register a side effect for this action so it doesn't do anything.
API
useReducerWithSideEffects(reducer, effect, initialValue?, init?)
The only difference between this function and useReducer
is the effect function
you need to provide as the second argument.
Effect Function
function(state, action) => Promise<action|undefined>
The effect function is called with the updated state and the action that caused the state update.
It needs to return a Promise
(async/await works great here) with either a action if you want to update the state again (i.e. after fetching from an API) or undefined
if you do not want to update the state (i.e. if you want to use a side effect for logging purposes only)
Advanced
createSideEffectReducer
It is possible to use the higher order function createSideEffectReducer
which takes the effect function
as it's only argument to create a hook which provides you with the exact API signature that useReducer
uses.
This way you can provide a nice abstraction for shared functionality like logging.
const useLoggingReducer = createSideEffectReducer(async (state, action) => {
console.log("Action", action);
console.log("Current State", state);
});
const MyComponent = () => {
const [state, dispatch] = useLoggingReducer(reducer, {
count: 0
});
};
Examples
🔗 API Call
Use the effect function to handle data fetching from an API
🔗 Logging
Use createSideEffectReducer to create a useReducer like hook that logs all actions and state changes.
Acknowledgments
This library is greatly inspired by the idea of ngrx effects, which is an awesome library if your working with Angular.