Kwinten Pisman
Freelance frontend architect. Occasional blogger. Reactive fan.
By Kwinten Pisman
@KwintenP
Kwinten Pisman
Frontend architect
Blogger - https://blog.kwinten.com
@KwintenP
Data state
Application state
Inner state
Simple component tree without state management
Component tree with models
Problems
Connectivity between models
Everyone can update from anywhere
Changes in one place trigger changes somewhere else
Everyone can still update the model from anywhere
How are changes from one place reflected elsewhere
==> NO CLEAR DATAFLOW!
Model
Listens to state from the model
(read-only)
Components
Sends messages to the dispatcher to update the model
Dispatcher
Listens to messages being sent
Updates itself and notifies components
Predictable state container for javascript apps
Created by Dan Abramov
An architecture
Unidirectional dataflow
Brings structure to state
An Architecture, not a framework
Created at Facebook
Unidirectional data flow
Brings structure to your data
Easier to reason about
Model
Subscribes to state from the model
Components
Sends messages to the dispatcher to update the model
Dispatcher
Listens to messages being sent
Updates itself and notifies components
Store
Sends ACTIONS to the dispatcher to update the model
Listens to ACTIONS being sent
Reducers
Updates itself via the reducers and notifies components
Single source of truth
State is immutable
Changes are made through reducers which are pure functions
Both are Redux implementations
Frameworks
Redux for all javascript apps
NGRX/Store integrated with Angular 2
Code you write is interchangeable!
Subscribes to state from the model
Components
Dispatcher
Store
Sends ACTIONS to the dispatcher to update the model
Listens to ACTIONS being sent
Reducers
Updates itself via the reducers and notify components
let action: Action = {
type: "ADD_TWEET",
payload: {
tweet: {
id: 1,
userName: "@KwintenP",
content: "I'm giving a talk at
#jsbe on #redux",
starred: true
}
}
}function tweetsReducer(
state: State = [],
action: Action): Tweet[] {
switch (action.type) {
case "ADD_TWEET":
return [...state, action.payload.tweet];
default:
return state;
}
}
Store is the single source of truth
Holds all the state in one object
Subscribe to it to get state updates
Store calls reducers with an action to create a new state object
Initiate XHR request
Store
Dispatcher
SET_BUSY_FLAG
Receive result from XHR request
REMOVE_BUSY_FLAG
Handle result
ADD_DATA
{
tweets: [],
busyFlag: true
}{
tweets: [],
busyFlag: false
}{
tweets: [
{
id: 1,
username: "@KwintenP",
content: "I'm giving
a talk at JSBE",
starred: true
}
],
busyFlag: false
}Important to do up front
Divide it between ui and data state
As you would design a database
No double entries - Normalise if needed
Not everything should be put into the store!
Be pragmatic!
| Do | Don't | |
|---|---|---|
| Ui | Component state for rerender |
Everything else |
| Data | Shared State State to cache Rehydration state |
Everything else |
const stateExample = {
ui: {
mainPage (smart component): {
sidebarCollapsed: true,
topbarCollapsed: true
}
},
data: {
tweets: [....],
users: [....]
}
}Root
ui
data
mainPage
tweets
users
const stateExample = {
ui: { -> uiReducer
mainPage (smart component): { -> mainPageReducer
sidebarCollapsed: true,
topbarCollapsed: true
}
},
data: { -> dataReducer
tweets: [....], -> tweetsReducer
users: [....] -> usersReducer
}
}export function dataReducer(state: DataState = initialTweetState, action: Action): DataState {
switch (action.type) {
case ADD_TWEET:
case REMOVE_TWEET:
case SET_TWEETS:
case UPDATE_TWEET:
case TOGGLE_STAR_TWEET:
return Object.assign(
{},
state,
{tweets: tweetsReducer(state.tweets, action)}
);
case ADD_USER:
case REMOVE_USER:
return Object.assign(
{},
state,
{users: usersReducer(state.tweets, action)}
);
default:
return state;
}
}
//BEFORE:
let beforeState: DataState = {
users: [],
tweets: [
{id: 1, ... }
]
}
dataReducer(beforeState, {type: "ADD_USER", payload: {user: {id: 42, ...}}});
//AFTER
let afterState: DataState = {
users: [
{id: 42, ...}
],
tweets: [ //--> SAME REFERENCE!
{id: 1, ... }
]
}
export const rootReducer = {
ui: uiReducer,
data: dataReducer
}
new Store(combineReducers(rootReducer));
// pseudo code
uiReducer(state.ui, action);
dataReducer(state.data, action);Middleware
Middleware
Reducer
State
Action
New
State
Logging
Performance metrics
Authentication
Server side rendering
Devtools - Time travelling
Separate development view layer - state layer
Reuse of the state layer on the server side
Dumps of state for bugs
Ideal for hot module reloading
Single store with single state object
State MUST be immutable
Updates to the store are done by dispatching actions
The store is updated via Reducers
Redux-thunk?
Redux selectors
Action creators
search(query: string): Observable<WineComSearchResult> {
return this.http
.get(`${WINE_COM_API_URL}catalog?apikey=${WINE_COM_API_KEY}&search=${query}`)
.map((resp: Response) => JSON.parse(JSON.stringify(resp.json()), camelCaseReviver));
}By Kwinten Pisman
Freelance frontend architect. Occasional blogger. Reactive fan.