A predictable state container for JavaScript apps
Redux Facts
-
Redux !== React
Redux is a standalone library.
Redux is framework agnostic.
-
Redux !== Flux
Flux is an architectural pattern.
Redux is an implementation.
-
Don't believe the hype, Redux is simple
It's 2KB in size and has a tiny surface API. Let me prove it to you.
- Redux can scale with the complexity of your app
A Functional Approach
What are the advertised benefits to a functional approach?
- Taming side effects
- Reducing state changes
- Greater predictability
- Easier extensibility
- Improved testability
- Minimizing moving parts
PluralSight - Functional Programming with C# (1.5 hours)
How tiny is Redux?
Breaking down "reducers"
function add(a, b) {
return a + b;
}
add(5, 5);
//returns 10
Breaking down "reducers"
var state = { total: 0 };
function impureAdd(a, b) {
state.total = a + b;
return state.total;
}
impureAdd(5, 5, state);
//returns 10
Breaking down "reducers"
function calc(a, b, command) {
if(command === "ADD") {
return a + b;
} else if(command === "SUBTRACT") {
return a - b;
}
}
calc(5, 5, "ADD");
//returns 10
Breaking down "reducers"
//This reducer is impure
function reducer(state, action) {
switch(action.type) {
case 'ADD_COUNTER':
state.total += action.value;
return state;
}
}
var stateBefore = { total: 5 };
var action = {type: 'ADD_COUNTER', value: 5 }
var stateAfter = reducer(stateBefore, action);
console.log(stateAfter.total);
//returns 10
Breaking down "reducers"
//This reducer is pure
function reducer(state, action) {
switch(action.type) {
case 'ADD_COUNTER':
var newState = {};
newState.total = state.total + action.value;
return newState;
}
}
var stateBefore = { total: 5 };
var action = {type: 'ADD_COUNTER', value: 5 }
var stateAfter = reducer(stateBefore, action);
console.log(stateAfter.total);
//returns 10
Breaking down "reducers"
//ES6 Object.assign
function reducer(state, action) {
switch(action.type) {
case 'ADD_COUNTER':
var newState = Object.assign({}, state);
newState.total = state.total + action.value;
return newState;
}
}
var stateBefore = { total: 5 };
var action = {type: 'ADD_COUNTER', value: 5 }
var stateAfter = reducer(stateBefore, action);
console.log(stateAfter.total);
//returns 10
Breaking down "reducers"
//ES next object spread
function reducer(state, action) {
switch(action.type) {
case 'ADD_COUNTER':
return {...state, total: state.total + action.value };
}
}
var stateBefore = { total: 5 };
var action = {type: 'ADD_COUNTER', value: 5 }
var stateAfter = reducer(stateBefore, action);
console.log(stateAfter.total);
//returns 10
Breaking down "reducers"
// Bring in Redux!
var createStore = require('redux').createStore;
// Define a reducer
function reducer(state, action) {
if(!action.type) { return state; }
switch(action.type) {
case 'ADD_COUNTER':
return {...state, total: state.total + action.value };
default:
return state;
}
}
var store = createStore(reducer, { total: 5 });
store.dispatch({type: 'ADD_COUNTER', value: 5 }); //10
store.dispatch({type: 'ADD_COUNTER', value: 5 }); //15
console.log(store.getState()); //{ total: 15 }
Breaking down "reducers"
// Bring in Redux!
import { createStore } from 'redux';
// Define a reducer
function reducer(state, action) {
if(!action.type) { return state; }
switch(action.type) {
case 'ADD_COUNTER':
return {...state, total: state.total + action.value };
default:
return state;
}
}
const store = createStore(reducer, { total: 5 });
store.subscribe(function() {
console.log('Value changed!', store.getState());
});
store.dispatch({type: 'ADD_COUNTER', value: 5 });
Reducer Facts
-
Reducers must be synchronous
-
Reducers must not dispatch actions
-
Reducers should be pure
- Reducers are deterministic
A Sample App
Using nothing but Redux and the DOM
How do I async?
// Inside your app
// <button onclick="dispatchAction()">Do Work</button>
function dispatchAction() {
//Show loading indicator
store.dispatch({ type: "REQUEST_STARTED" });
//Make AJAX call
$.ajax({ url: "/foo/bar" })
.done((data) => {
//Remove loading indicator, update with new data
store.dispatch({ type: "REQUEST_COMPLETED", value: data });
});
}
How about validation?
function dispatchAction() {
//Show loading indicator
store.dispatch({ type: "REQUEST_STARTED" });
//Perform synchronous validation
store.dispatch({ type: "VALIDATE_FORM" });
if(!store.getState().isValid) { return; }
//Make AJAX call
$.ajax({ url: "/foo/bar" })
.done((data) => {
//Remove loading indicator, update with new data
store.dispatch({ type: "REQUEST_COMPLETED", value: data });
});
}
//ES next goodness
async function emailChanged(email) {
store.dispatch({ type: "UPDATE_EMAIL", value: email });
const emailValid = await $.ajax({ url: "/is/email/valid" });
store.dispatch({ type: "EMAIL_VALID_RECD", value: emailValid });
}
How Redux Fits Into Your App
"App"
Controller View
Store
State
Child
Component
Child
Component
Child
Component
Reducer(s)
UI components only reflect the state passed to them.
Logic lives in the reducers.
dispatch
subscribe
How to Scale Redux
- You can break up large reducers into separate modules/files
- You can use multiple reducers via "combineReducers"
//Reducer 1
export default function todos(state = [], action) { ... }
//Reducer 2
export default function counter(state = 0, action) { ... }
//Combine Reducers
const combinedReducer = combineReducers({ todos, counter });
//Create the store
const store = createStore(combinedReducer);
store.getState(); // returns { todos: [], counter: 0 }
//Each reducer is handed its own branch of the state tree
Can you tell me what this app does?
// actionTypes.js
export default {
ADD_ITEM: "ADD_ITEM",
REMOVE_ITEM: "REMOVE_ITEM",
EMPTY_CART: "EMPTY_CART",
CART_LOADED: "CART_LOADED",
CREATING_ORDER: "CREATING_ORDER",
ORDER_CREATED: "ORDER_CREATED"
}
For Really, Really Big SPAs...
This is for apps that have large teams divided by products that ship sub-apps within a single enclosing "app shell".
Action Creators
// actions.js
export function addCounter(amount) {
return { type: actionTypes.ADD_COUNTER, value: amount };
}
export function resetCounter() {
return { type: actionTypes.RESET_COUNTER };
}
// app file
import * as actions from './actions';
//store.dispatch({ type: 'ADD_COUNTER', value: 5 });
store.dispatch(actions.addCounter(5));
Action Creators help define the shape of your actions
Async with Thunks
// What is a thunk?
// In this context it's...
// 1. a function that returns a function
function thunk() {
/* outer function */
return function() { /* inner function */ };
}
// 2. that dispatches actions
function saveProfile() {
return function (dispatch, getState) {
dispatch(actions.savingProfile());
$.ajax({...})
.done(() => dispatch(actions.profileSaved()));
};
}
Async with Thunks
// Inside your app
import * as actions from './actions';
// <button onclick="store.dispatch(actions.doWorkThunk())">...
// inside ./actions
function doWorkThunk() {
return (dispatch, getState) => {
//Show loading indicator
store.dispatch({ type: "REQUEST_STARTED" });
//Make AJAX call
$.ajax({ url: "/foo/bar" })
.done((data) => {
//Remove loading indicator, update with new data
store.dispatch({ type: "REQUEST_COMPLETED", value: data });
});
}
}
Boilerplate and Best Practices
Connect, Mapping Dispatch, Mapping State, Binding Action Creators
Learn More...
- Great documentation on the Redux.org site
http://redux.js.org/ - Dan Abramov's Egghead.io videos
https://egghead.io/lessons/javascript-redux-the-single-immutable-state-tree?play=yes - Cory House's Building Applications with React and Redux
https://www.pluralsight.com/courses/react-redux-react-router-es6 -
Rangle.io's Managing State with Redux and Angular 1.x
http://blog.rangle.io/managing-state-redux-angular/ - Microsoft's React + Redux SPA for .NET Core
https://github.com/aspnet/JavaScriptServices/tree/dev/templates/ReactReduxSpa - Dan Abramov "You Might Not Need Redux"
https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367#.mefi4bge3
More Resources
- Why use Redux over Facebook flux?
http://stackoverflow.com/a/32920459/76840
- Babel REPL
https://babeljs.io/
- Runkit - play with Redux (and any other npm module) in your browser
https://runkit.com/npm/redux
- Redux DevTools Chrome Extension
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en
- Form Validation with Redux
https://medium.com/@aikeru/form-validation-with-redux-d1acd4d405a1#.azzxtcxe5
Questions?
Thanks for your time.
Redux: A Predictable State Container for JavaScript Apps
By Michael Snead
Redux: A Predictable State Container for JavaScript Apps
A presentation on Redux for the Jacksonville node.js meetup group.
- 2,089