Flow Control in Redux
@EmaSuriano
Written by:
Summary
- What is middleware?
- Basic Structure
- Next vs Dispatch
- Connecting to Redux
- Environment Configuration
- Advantages
- Building Middlewares
- MeasureMiddleware
- APIMiddleware
- CustomMiddleware (Flow Control)
- MOTY's
- Redux-thunk
- Redux-sagas
What is Middleware?
Redux Doc.
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
Simple Word
Allow us to add code that will run before action is passed to the reducer.
Basic Structure
const myMiddleware = ({ getState, dispatch }) => next => action => {
/*
before reducers have run
*/
next(action); // will propagate the action down the middleware chain
/*
after reducers have run
*/
};
Next
Will pass the action alogn the middleware chain (from the current) down to the reducer.
Dispatch
Will start the action flow from the beggining (the first middleware in the), so it will eventualyy reach the current one as well.
A
B
C
next()
Red
dispatch()
action
Middleware Chain
Connecting to Redux
import { createStore, applyMiddleware } from 'redux';
import reducer from 'reducers/root';
import thunk from 'redux-thunk';
import measureMiddleware from 'middleware/measure';
const middlewareChain = applyMiddleware(measureMiddleware, thunk);
const store = createStore(reducer, middlewareChain);
By calling applyMiddleware and passing our middlewares, we are defining the middleware chain for the application. The order is defined by the middleware's order.
Environment Configuration
const middleware = [apiMiddleware]; //middlewares that will run everywhere
if (development) {
middleware.push(measureMiddleware); // only on dev
} else {
middleware.push(analyticsMiddleware);
}
const store = createStore(reducer, applyMiddleware(...middleware));
We have the ability to defined which middlewares will run inside our differents enviroment. For example, we want to use the measureMiddleware only in Dev.
Advantages of using them
- Abstraction of actionCreators
- Responsibilities Splitting
- Flow/Side effects Control
- Testeability
Building a Middleware
Measure Middleware
const measureMiddleware = () => next => action => {
console.time(action.type);
next(action);
console.timeEnd(action.type);
};
export default measureMiddleware;
This middleware allow us to potentially catch poorly performing reducer implementations. We start the timing before running an action, then we tell the browser to print the timing after the action is done.
API Middleware
Our goal is to create a generic middleware that can serve any API request and requires only the information passed to it in an action.
To use our middleware we need to craft a special action object for it:
API Middleware
In this case, this middleware will manage any API request and requires only the information passed to it in an action.
To use our middleware we need to craft some special actions object for it:
const fetchUser = (id, cancelableId) => ({
type: API,
url: `user/${id}`,
next: SET_USER,
cancelableId,
});
const cancelAPI = (id) => ({
type: CANCEL_API,
id,
});
Dynamic Action Types
With this approach we can use the sme action type to handle three cases for async actions
const asyncActionType = (type) => ({
PENDING: `${type}_PENDING`,
SUCCESS: `${type}_SUCCESS`,
ERROR: `${type}_ERROR`,
});
export const LOGIN = asyncActionType('LOGIN');
export const FETCH_RECIPES = asyncActionType('FETCH_RECIPES');
const canceled = {};
const apiMiddleware = ({ dispatch }) => (next) => (action) => {
const handleResponse = (data) => {
if (action.cancelable && canceled[action.cancelable]) {
return;
}
dispatch({ type: action.payload.next.SUCCESS, payload: data })
};
if (action.type === API_REQUEST) {
fetch(action.payload.url)
.then((response) => response.json())
.then(handleResponse);
dispatch({ type: action.payload.next.PENDING });
}
if (action.type === CANCEL_API_REQUEST) {
canceled[action.id] = true;
setTimeout(() => delete canceled[action.id], 5000);
}
return next(action);
};
Custom Middleware for Flow Control
const MY_ACTION = {
type: 'MY_ACTION',
};
const doSomething = (dispatch) => {
dispatch(DO_SOMETHING);
dispatch(MY_ACTION);
}
const doMoreStuff = (dispatch) => {
dispatch(DO_MORE_STUFF);
dispatch(MY_ACTION);
}
const doWhateverYouLike= (dispatch) => {
dispatch(DO_WHATEVER_YOU_LIKE);
dispatch(MY_ACTION);
}
In this case, we're going to create a Middleware to help us managing side-effects on out application.
Let's suppose that we have an action (MY_ACTION) that needs to be dispatch after a series of actions.
const MY_ACTION = {
type: 'MY_ACTION',
};
const doSomething = (dispatch) => {
dispatch(DO_SOMETHING);
}
const doMoreStuff = (dispatch) => {
dispatch(DO_MORE_STUFF);
}
const doWhateverYouLike= (dispatch) => {
dispatch(DO_WHATEVER_YOU_LIKE);
}
const customMiddleware = ({ getState, dispatch }) => next => action => {
switch(action.type) {
case DO_SOMETHING:
case DO_MORE_STUFF:
case DO_WHATEVER_YOU_LIKE:
dispatch(MY_ACTION);
default: break;
}
next(action);
};
Middlewares of the Years
most used middlewares
Redux-thunk
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
Allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met.
Inside Redux-thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Redux-saga
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;
Aims to make side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) in React/Redux applications easier and better.
Connecting with Redux
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// then run the saga
sagaMiddleware.run(mySaga)
// render the application
Questions?
Thanks!
Copy of FlowControlRedux
By Pablo Hiyano
Copy of FlowControlRedux
Introduction to middlewares in Redux, and some examples of them created by the community.
- 417