Redux
basics
by Elizaveta Anatskaya
The need for flux
zombie notification
It was 2011 and the engineers at Facebook had a problem: users would see that they had a chat notification, but when they clicked on it there would be no new message waiting for them.
The number of models and views had compounded as Facebook added new features, and the relationships between them had become unmanageably difficult to keep track of.
2014, Flux was an entirely new approach to state management. It’s goal was to make changes in state predictable
How Flux Works
All data must flow in only one direction. This unidirectional data flow is a significant part of what keeps everything so predictable.
- Any Flux application only ever has one dispatcher.
- Every action is sent to every store.
- State must only be changed (or mutated) in response to a received action.
Flux is a generalized pattern, not a specific library
Introducing Redux
Redux aims to simplify and streamline many of the concepts introduced by Flux.
Redux only has a single store (more on that in a moment) so there is only a single destination to broadcast new actions to, thus eliminating the need for a dispatcher.
No Dispatcher
– pure functions that accept the current state and a given action as arguments, and which output either the unmodified state or a new, edited copy of the state.
Reducers
const counter = (state = { counter: 0 }, action) => {
switch (action.type) {
case 'INCREMENT_COUNTER':
return {
...state,
counter: state.counter + 1
}
default:
return state
}
}
🍆 A single source of truth (“global state”)
Guiding principles
🍆 State is read-only (“actions”)
🍆 Changes are made with pure functions (“reducers”)
Redux is used to manage data in a React application. Using redux means you create a store that a React app listens to.
What about React state?
“I thought React already had state, why do I need a separate tool to do it?” - you
🍆 Lack of organization and clarity
🍆 Handling state that the whole application needs.
Fundumentals
🟣 Actions/Action Creators
🟣 Dispatch
🟣 Reducers
🟣 Store
Analogy
2. Restaurant server → “dispatch”
1. Your Order → “action”
3. Chef → “reducer”
Stores
Think of a store as a bunch of data. In fact, you may have already done this: the example here is a plain JS object that stores data.
let album = {
title: 'Kind of Blue',
artist: 'Miles Davis',
year: 1959
}
You can read and write data into a plain JS object. You can do the same in Redux, but just a little differently.
//read
console.log(album.title)
--> "Kind of Blue"
//write
album.genre = 'Jazz'
console.log(album)
--> {
"title":"Kind of Blue",
"artist":"Miles Davis",
"year":1959,"genre":"Jazz"
}
Our first store
Stores are created using createStore()
import { createStore } from 'redux'
const reducer = {...}
let store = createStore(reducer, album)
You can read from a store by checking its state using getState(). Writing data works a bit different, though. That's where reducer comes in.
The function you pass to createStore() is called a reducer. It's a simple function that takes in a state, and returns another state.
let createStore = require('redux').createStore
let initialState = {
title: 'Kind of Blue',
artist: 'Miles Davis',
year: 1959
}
function reducer (state, action) {
return state
}
let store = createStore(reducer, initialState)
// Read
let state = store.getState()
console.log(state.title)
// --> "Kind of Blue"
Reducer
The function you pass to createStore() is called a reducer. It's a simple function that takes in a state, and returns another state.
reducer(state, action) --> state
Most reducers use switch inside them to do different things depending on the action.
function reducer (state, action) {
switch (action.type) {
case 'PUBLISH':
return { ...state, published: true }
case 'UNPUBLISH':
return { ...state, published: false }
case 'UPDATE':
return { ...state, ...action.data }
default:
return state
}
}
Updating the store
import { createStore } from 'redux'
let store = createStore(reducer, article)
function reducer (state, action) {
if (action.type === 'PUBLISH') {
return { ...state, published: true }
} else {
return state
}
}
You can't change the store's state from outside the store. To do that, you'll need to create actions. Actions are made through reducer functions, which is used by createStore().
Actions are objects and always have a type key.
Reducers take the current state and return a new one. How it changes the store depends on action.
To run an action, use dispatch(). This changes the store's state.
let createStore = require('redux').createStore
function reducer (state, action) {
switch (action.type) {
case 'PUBLISH':
return Object.assign({}, state, { published: true })
default:
return state
}
}
let article = { title: 'Global Warming' }
let store = createStore(reducer, article)
console.log(store.getState())
store.dispatch({ type: 'PUBLISH' })
console.log(store.getState())
//--> {"title":"Global Warming"}
//--> {"title":"Global Warming","published":true}
The ... symbol is the object spread operator. It's available in Babel and in the 2017 version of JavaScript. These two are roughly equivalent.
return { ...state, published: true }
return {
title: state.title,
body: state.body,
published: true
}
//The contents of state is rolled out in place of ...state.
The spread operator
The only way to change the store's state is by dispatching actions. You can then easily make a log of what actions have happened, or even undo them.
let createStore = require('redux').createStore
function reducer (state, action) {
switch (action.type) {
case 'SET':
return action.value
case 'ADD':
return state + action.value
default:
return state
}
}
// The second parameter (initial state) is optional.
let store = createStore(reducer)
store.dispatch({ type: 'SET', value: 200 })
store.dispatch({ type: 'ADD', value: 5 })
console.log(store.getState())
//--> 205
Dispatching actions
let createStore = require('redux').createStore
function reducer (state, action) {
switch (action.type) {
case 'SET':
return action.value
case 'ADD':
return state + action.value
default:
return state
}
}
// The second parameter (initial state) is optional.
let store = createStore(reducer)
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch({ type: 'SET', value: 200 })
store.dispatch({ type: 'ADD', value: 5 })
// --> 200
// --> 205
You can also listen for changes in the store using subscribe()
Action creators
In a typical app, we'll likely have a few of action creators. It's best to organize these into one file.
export function loadProject (id) {
return function (dispatch) {
dispatch({ type: 'PROJECT_LOADING', data })
return fetch(`/projects/${id}`)
.then(data => dispatch({ type: 'PROJECT_LOADED', data }))
.catch(err => dispatch({ type: 'PROJECT_ERROR', err }))
}
}
export function saveProject (id) { ... }
export function deleteProject (id) { ... }
export function createProject (id, data) { ... }
import { loadProject } from './actions'
store.dispatch(loadProject())
Action Creators are functions that return an action. loadProject() and friends return functions, which redux-thunk will happily accept as actions.
import { createStore } from 'redux'
store = createStore(reducer, {initial state})
Recap
Stores are made from reducer functions.
Actions are dispatched to the reducer.
Reducers tell us how to change a state based on an action.
States: the store keeps a state, and you can listen for updates using subscribe().
store.dispatch({ type: 'PUBLISH' })
function reducer (state, action) {
if (action.type === 'PUBLISH') {
return { ...state, published: true }
}
return state
}
store.getState().published
store.subscribe(() => { ... })
To be continued
🍆🍆🍆
Redux
By Elizabeth Anatskaya
Redux
- 277