Redux
Predictable State Container
more on that in just a sec
Runs Everywhere
JavaScript does (browser, server, native)
View Library Agnostic
though often found with React
Excellent Docs & Videos
I'm totally plagiarizing rn
Packs a Punch
at ~ 2kB
Predictable State Container
Predictable
State
Container
Not a Database
not managed by redux
SELECT * FROM `player`
WHERE `name` LIKE 'Jared A%'
Not Server Session "State"
not managed by redux
HTTP/1.0 200 OK
Set-Cookie: LSID=DQAAAK…Eaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
Set-Cookie: HSID=AYQEVn….DKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly
Set-Cookie: SSID=Ap4P….GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
Not Component Internal State
not managed by redux
like :hover or :active state
What Then?
Application State
- in memory data assignment (eg: var, let, const)
- that has app significance
- is referenced asynchronously
- and may change asynchronously
const scores = {
Jared: Infinity,
Bruce: 1,
Matt: NaN
};
Application State
not app state
- drop-down is open
- what's being hovered over
is app state
- list of options
- chosen option
example
Application State
may consist of:
- transformed server responses
- cached data
- locally created data
- complex UI state
- active route
- the active tab
- whether to show a spinner
- whether to show pagination
- alert messages / notifications
Predictable
State Container
If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. At some point, you no longer understand what happens in your app as you have lost control over the when, why, and how of its state. When a system is opaque and non-deterministic, it’s hard to reproduce bugs or add new features.
Managing State is Hard
http://redux.js.org/docs/introduction/Motivation.html
Opaque?
stuff is happening and you can't even
Non-deterministic?
the same set of actions produce different results
Insanity: doing the same thing over and over again and expecting different results.
add asynchronous behavior
and not in the good way
How is Redux Different?
Same Input Produces Same Output
Any Potential State Change is Clearly Declared Beforehand
Predictable State Container
We've Demonstrated some of the Complexities of State Management
So Obviously the Smart Thing to do is Let State Run Freely and Loose
When application state is decentralized and spread across your entire app - often hidden with behaviors, in functions, in closures, and/or bunch of global objects - it becomes even more impossible to manage as your app scales
Uncontained App State
class ScoreCard {
constructor(name, initialScore=0) {
this.name = name;
this.score = initialScore;
}
add(number) {
this.score += number;
}
}
In Redux, your whole app state is stored separately -- contained in an object tree inside a single store.
There is one way in
There is one way out
Predictability
Clarity
Manageability
Sanity
3 Principles
of Redux
http://redux.js.org/docs/introduction/ThreePrinciples.html
Single Source of Truth
The state of your whole application is stored in an object tree within a single store.
State is Read-Only
The only way to mutate the state is to emit an action, an object describing what happened.
Changes via Pure Functions
emitted actions (along with the current state) are passed to pure functions which return a new state
Super Simple API
Redux has a
Super Simple API
import {createStore} from 'redux';
// you import your reducers
// you spend most of your time writing reducers
// more on that in a minute...
import myReducer from './my/reducer.js';
// you create the redux store from reducer(s)
export const store = createStore(myReducer);
// you get current state at any time by...
store.getState();
// you subscribe to be notified when things change
store.subscribe( () =>
console.log(store.getState())
);
// dispatch "actions" asking store to change state...
// actions are just POJOS with a `type` and optional data
store.dispatch({type: "INCREMENT"});
Reducers
Reducers
(previousState, action) => newState
- a regular JavaScript Function
- that's a pure function
- accepts previous state and action
- returns a new state
- returns the previous state for any unknown action.
Reducers
Things you should never do inside a reducer:
- Mutate its arguments
- Perform side effects
- API calls
- routing transitions
- Call non-pure functions
- Date.now()
- Math.random().
Given the same arguments, it should
calculate the next state and return it.
No surprises. No side effects. No API calls. No mutations.
Just a calculation
Example Reducer
export default (state = 0, {type}) => {
switch(type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
Did we:
- mutate any args?
- perform side effects?
- call non-pure functions?
- return a new state?
Redux Visualized
{type}
redux internal state tree
{type}
(state,action)
=> newState
reducer
Live Coding
import {createStore} from 'redux';
const myReducer = (state = 0, {type}) => {
switch(type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
const store = createStore(myReducer);
store.subscribe( () =>
console.log(store.getState())
);
store.dispatch({type: "INCREMENT"});
store.dispatch({type: "DECREMENT"});
Answer
Actions & Action Creators
Actions
- payloads of information that send data from your application to your store
- are POJOs
- must have a unique type property
- may be accompanied by minimal data
const action = { type: 'ADD', number: 10 };
dispatch(action);
Action Creators
- functions that create actions
- abstract store from app
const add = (number) =>
({ type: 'ADD', number });
dispatch( add(10) );
don't write these
write these
Live Coding
RoadTrip Dad
add 10...
divide 2...
multiply 5...
multiply 4...
divide 10...
minus 1...
divide 3...
...what do we have?
start 0...
/* Task #1 */
const roadTripDad =
(state = 0, {type, number}) => {
switch(type) {
case 'START':
return number;
case 'ADD':
return state + number;
case 'SUBTRACT':
return state - number;
case 'MULTIPLY':
return state * number;
case 'DIVIDE':
return state / number;
default:
return state;
}
}
/* Task #2 */
const start = number =>
({type: 'START', number});
const add = number =>
({type: 'ADD', number});
const subtract = number =>
({type: 'SUBTRACT', number});
const multiply = number =>
({type: 'MULTIPLY', number});
const divide = number =>
({type: 'DIVIDE', number});
/* Task #3 */
const store = createStore(roadTripDad);
store.subscribe(() => {
render(
store.getState(),
number =>
store.dispatch(add(number)),
number =>
store.dispatch(subtract(number)),
number =>
store.dispatch(multiply(number)),
number =>
store.dispatch(divide(number))
)
});
/* Task #4 */
store.dispatch(start(10))
Combine Reducers
So, the entire app's state is contained in one reducer?
No
Redux Shines at Scale
Combine Reducers
// import createStore AND combineReducers
import {createStore, combineReducers} from 'redux';
// import all your apps' reducers
import talks from '../reducers/talks.js';
import speakers from '../reducers/speakers.js';
import sessions from '../reducers/sessions.js';
// create the state tree (aka: root reducer)
const rootReducer = combineReducers({talks, speakers, sessions});
// create the redux store from reducer(s)
export const store = createStore(rootReducer);
console.log( store.getState() );
/* {talks: ..., speakers: ..., sessions: ...}; */
Visualized
Combine Reducers
{type}
redux internal state tree
{type}
(state,action)
=> newState
(state,action)
=> newState
{type}
reducer
reducer
Async Behavior
things you should never do inside a reducer
API Calls
Doing So Makes them Non-Deterministic, Unpredictable & Opaque
Where is Async Managed?
...
Live Coding
Answer
Middleware
a third-party extension point between dispatching an action, and the moment it reaches the reducer.
Visualized
Middleware
{type}
redux internal state tree
{type}
(state,action)
=> newState
()=>{}
middleware
reducer
What Does That Mean?
- you write reducers the exact same way
- you can intercept & act on actions coming in
- you still dispatch regular POJOs
- you can also dispatch in other things
- thunks
- promises
- whatever the middleware supports
What It Looks Like
import { createStore, combineReducers, applyMiddleware } from 'redux';
import reducers from '../path/to/reducers.js';
// middleware that lets you dispatch thunks
import thunk from 'redux-thunk';
const rootReducer = combineReducers(reducers);
export const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Using Redux Thunks
// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
store.dispatch(
makeASandwichWithSecretSauce('My wife')
)
Redux
By Jared Anderson
Redux
- 2,134