Redux
Introduction and usage in
Motivation
- Managing state is hard
- Managing state in a asynchronous world is even harder.
- We are mixing mutation and asynchronicity.
Three Principles
-
Single Source of truth
-
State is Read Only
-
Mutation
Single Source of Truth
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
State is Read Only
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
})
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
})
Mutations written as pure functions
(state, action) => state
Also known as Reducers
The Basics
-
Actions
-
Reducers
-
Store
But first: Demo Time
Actions
const ADD_MESSAGE = 'ADD_MESSAGE';
{
type: ADD_MESSAGE,
text: "Hello World!",
channel: '#redux'
}
{
type: LIKE_MESSAGE,
index: 5
}
{
type: SET_CURRENT_CHANNEL,
channel: '#redux'
}
Action Creators
function addMessage(text, channel) {
return {
type: ADD_MESSAGE,
text,
channel,
}
}
dispatch(addMessage(text);
const boundAddMessage = (text) => dispatch(addMessage(text));
boundAddMessage(text);
Reducers
Designing the State Shape
{
currentChannel: '#redux',
messages: [
{
text: 'Hello World',
likes: 8,
channel: '#redux',
},
{
text: 'Heisann, Hoppsann',
likes: 73,
channel: '#redux',
},
{
text: 'Yo!',
likes: 2,
channel: '#random',
},
{
text: 'This is epic',
likes: 3,
channel: '#random',
}
]
}
Reducers
(prevState, action) => newState
const initialState = {
currentChannel: '#redux',
channels: []
}
function miniWoop(state, action) {
if (typeof state === 'undefined') {
return initialState;
}
return state;
}
const initialState = {
currentChannel: '#redux',
channels: []
}
function miniWoop(state = initialState, action) {
return state;
}
function miniWoop(state = initialState, action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return {
...state,
currentChannel: action.channel,
}
default:
return state;
}
}
function miniWoop(state = initialState, action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return {
...state,
currentChannel: action.channel,
}
case ADD_MESSAGE:
return {
...state,
messages: [
...state.messages,
{
text: action.text,
likes: 0,
channel: action.channel,
}
}
}
default:
return state;
}
}
function miniWoop(state = initialState, action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return {
...state,
currentChannel: action.channel,
}
case ADD_MESSAGE:
return {
...state,
messages: [
...state.messages,
{
text: action.text,
likes: 0,
channel: action.channel,
}
]
}
case LIKE_MESSAGE:
return {
...state,
messages: [
...state.messages.slice(0, action.index),
{
...state.messages[action.index],
likes: state.messages[action.index].likes + 1
},
...state.messages.slice(action.index + 1),
]
};
default:
return state;
}
}
function messages(state = [], action) {
switch (action.type) {
case ADD_MESSAGE:
return [
...state,
{
text: action.text,
likes: 0,
channel: action.channel,
}
]
case LIKE_MESSAGE:
return [
...state.slice(0, action.index),
{
...state[action.index],
likes: state[action.index].likes + 1
},
...state.slice(action.index + 1),
];
default
return state;
}
}
function miniWoop(state = initialState, action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return {
...state,
currentChannel: action.channel,
}
case ADD_MESSAGE:
case LIKE_MESSAGE:
return {
...state,
messages: messages(state.messages, action),
};
default:
return state;
}
}
function currentChannel(state = '#redux', action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return action.channel;
default:
return state;
}
}
function miniWoop(state = {}, action) {
return {
currentChannel: currentChannel(state.currentChannel, action),
messages: messages(state.messages, action),
}
}
const miniWoop = combineReducers({
currentChannel,
messages,
});
Store
- Holds application state
- A single store in a Redux application
- getState()
- dispatch()
- subscribe(listener)
import { createStore } from 'redux';
import { miniWoop } from './reducers';
let store = createStore(miniWoop);
console.log(store.getState());
// Every time the state changes, log it
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// Dispatch some actions
store.dispatch(addMessage('Hello World', '#redux'));
store.dispatch(addMessage('This is a message', '#redux'));
store.dispatch(addMessage('Woop, Woop!', '#redux'));
store.dispatch(addMessage('4!', '#random'));
store.dispatch(likeMessage(0));
store.dispatch(likeMessage(1));
store.dispatch(likeMessage(2));
store.dispatch(likeMessage(2));
store.dispatch(setCurrentChannel('#random'));
// Stop listening to state updates
unsubscribe();
DEMO TIME
Data Flow
- You call store.dispatch(action)
- Redux calls the root reducer function
- The root reducer function might combine multiple recuce functions.
- Redux saves the new state, and notifies all listeners.
Usage With React
- react-redux
- Smart and Dumb Components
const MessageList = ({ messages, dispatch }) =>
<table border="1">
<thead><tr><th>Likes</th><th></th><th>Text</th></tr></thead>
<tbody>
{ messages.map( (message) =>
<Message
message={message}
key={message.index}
index={message.index}
dispatch={dispatch} />) }
</tbody>
</table>
Dumb Components
import { connect } from 'react-redux'
const MiniWoop = ({ dispatch, messages, currentChannel }) =>
<div>
<ChannelChooser dispatch={dispatch} currentChannel={currentChannel}/>
<AddMessage dispatch={dispatch} currentChannel={currentChannel} />
<MessageList dispatch={dispatch} messages={messages} />
</div>
const ConnectedMiniWoop = connect(select)(MiniWoop);
Smart Components
const select = state => ({
currentChannel: state.currentChannel,
messages: state.messages
.map((msg, index) => ({
...msg,
index
}))
.filter(msg =>
msg.channel === state.currentChannel),
});
import { Provider } from 'react-redux'
import store from '../store'
const TopLevel = (props) =>
<Provider store={store}>
<ConnectedMiniWoop />
</Provider>
Provider
import { setCurrentChannel } from '../actions'
const ChannelChooser = ({ dispatch, currentChannel }) =>
<div>
{ channels.map((channel) =>
<Channel
channel={channel}
isCurrent={currentChannel === channel}
onClick={() => dispatch(setCurrentChannel(channel))} />)
}
</div>
const Channel = ({ channel, isCurrent, onClick }) =>
<button onClick={onClick} className={ isCurrent ? 'active' : '' }>{ channel }</button>
Dispatching
Async
Or "The stuff that makes things complicated"
Solved by "Thunk Action Creators"
export function requestChannels() {
return {
type: MessageConstants.CHANNELS_REQUEST,
};
}
export function receiveChannels(channels) {
return {
type: MessageConstants.CHANNELS_SUCCESS,
channels: channels,
};
}
export function fetchChannels() {
return dispatch => {
dispatch(requestChannels());
return xhr.get(urls.channels)
.then(channels => dispatch(receiveChannels(channels)));
};
}
Usage in Real World
AKA Woop
{
currentChannel: '#redux',
messages: [
{
text: 'Hello World',
likes: 8,
channel: '#redux',
},
{
text: 'Heisann, Hoppsann',
likes: 73,
channel: '#redux',
},
{
text: 'Yo!',
likes: 2,
channel: '#random',
},
{
text: 'This is epic',
likes: 3,
channel: '#random',
}
]
}
{
currentChannel: '#redux',
channels: [
'#redux': {
messages: [1,2]
},
'#redux': {
messages: [3,4]
}
]
messages: {
1: {
text: 'Hello World',
likes: 8,
},
2: {
text: 'Heisann, Hoppsann',
likes: 73,
},
3: {
text: 'Yo!',
likes: 2,
},
4: {
text: 'This is epic',
likes: 3,
}
}
}
Normalizr
.
├── actionsCreators
│ ├── AccountActionCreators.js
│ └── MessageActionCreators.js
├── app.js
├── components
│ ├── App.js
│ ├── WoopApp.js
│ └── ...
├── constants
│ ├── AccountConstants.js
│ └── MessageConstants.js
├── notifier.js
├── old_stores
│ ├── AccountStore.js
│ └── MessageStore.js
├── reducers
│ ├── accounts.js
│ ├── index.js
│ └── messages.js
├── router.js
├── urls.js
├── utils
│ ├── ...
└── ws-events.js
Changed from Actions
to ActionCreators.
Needs cleanup
App.js is new - the Provider
WoopApp.js - now the smart Component.
Added many new constants.
Old Stores for Reference
The Reducers.
Should probably change to redux-router.
WebSocket changed to dispatch to store
Questions?
https://slides.com/sindreij/redux
https://github.com/sindreij/miniwoop
Redux documentation: http://redux.js.org/docs/introduction/index.html
Redux
By sindreij
Redux
- 771