Redux Forms + Middleware
React Indy - 26 March 2018
Who is this dude?
- Full Stack Developer at Angie's List
- In the trenches of React code everyday
- Functional Programming advocate
- Cat Owner
What are we talking about?
- Redux Forms
- Redux Thunk + Promise Middleware (think async)
Redux Forms
Forms
- How we get input from users on the web
- Multiple Types
- text box
- select
- checkbox
- Validation needed
Combining HTML Forms and React
How do we do it???
Some Terminology first...
Controlled and Uncontrolled Components
Controlled Components
- Source of truth for form is component state
- Updating the component state === updating the source of truth
Uncontrolled Components
- Source of truth for form value is the DOM
- Updating the DOM === updating the source of truth
Source of truth === what submitted values are
What does that look like?
Cool but didn't you say REDUX Forms?
- values only live in component state, eg not shareable across entire application
- is possible to create Redux actions for when the fields are updated
- is possible to have reducers listen to those actions and store form values
- but.... Redux Forms can do this all for you!
Getting Started
(assuming you have a Redux application setup)
npm install --save redux-form
import { createStore, combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
const rootReducer = combineReducers({
// ...your other reducers here
// you have to pass formReducer under 'form' key,
// for custom keys look up the docs for 'getFormState'
form: formReducer
})
const store = createStore(rootReducer)
Demo
Lessons Learned
- Redux Form uses controlled components
- Redux Form stores fires actions when Form changes
- Redux Form stores form values in Redux Store
- Access to Store === Access to Form Values.
Questions?
Redux Thunk + Promise Middleware
Making Async in Redux easier
Redux Refresher
- Store has initial State
- Reducer defines actions which modify state
- UI dispatches actions
- Reducer updates store
- new store sends values to React Components
const exampleAction = {
type: 'EXAMPLE',
payload: [1,2,3]
}
const createExampleAction = () => {
return exampleAction
}
// UI
const ExampleComponents = props => (
<div>
<h3> props.exampleActionPayload </h3>
<button onClick={props.createExampleAction()} />
</div>)
//Reducer
initalState = {
exampleActionPayload: []
};
export default (state = initalState, action) => {
switch (action.type) {
case 'EXAMPLE'
return {
...state,
exampleActionPayload: action.payload
};
}
};
We're living in an async world
const createAsyncAction = (arg) => {
return {
type: 'ASYNC_ACTION',
payload: new Promise((resolve) => {
setTimeout(() => resolve(), 1000)
})
}
}
const callAPI = (arg) => {
return {
type: 'API_CALL',
payload: fetch('http://kylepotts.com')
}
}
If only....
export default (state, action) => {
if (typeof action.payload === "Promise") {
action.payload
.then(result => {
dispatch(promiseFulfilled(action.type, result));
})
.catch(err => {
dispatch(promiseFailed(action.type, err));
});
}
};
const promiseFulfilled = (actionType, result) => {
return {
type: `${actionType}_COMPLETE`,
payload: result
};
};
const promiseFailed = (actionType, failure) => {
return {
type: `${actionType}_FAIL`,
payload: failure
};
};
Good news, Redux Promise middleware can do this!
Getting Started
npm i redux-promise-middleware -s
//store creation
import { createStore, applyMiddleware, compose } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './redux/reducers';
export const history = createHistory();
const initialState = {};
const enhancers = [];
const middleware = [promiseMiddleware()];
const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);
export default createStore(rootReducer, initialState, composedEnhancers);
What now?
- Detects payload with Promise
- Dispatches a ACTIONNAME_PENDING action
- Promise resolves
- dispatches a ACTIONNAME_FUFILLED action
- includes promise results
- Promise rejects
- dispatches ACTIONAME_REJECTED
- includes errors
- dispatches ACTIONAME_REJECTED
- dispatches a ACTIONNAME_FUFILLED action
const initalState = {
fooResults: null,
fooErrors: null,
};
export default (state = initalState, action) => {
switch (action.type) {
case 'FOO_FULFILLED':
return {
...state,
fooResults: action.payload
}
case 'FOO_REJECTED':
return {
...state,
fooErrors: action.payload
}
default:
return state;
}
};
const foo = () => ({
type: 'FOO',
payload: new Promise(),
})
// dispatch(foo())
// Then these actions are eventually dispatched
{
type: 'FOO_PENDING'
}
---
{
type: 'FOO_REJECTED'
error: true,
payload: {
...
}
}
||
{
type: 'FOO_FULFILLED'
payload: {
...
}
}
Redux Thunk
A special kind of Action
Problem
How do we chain actions?
const step2Action = (name) => {
return {
type: 'STEP_2',
payload: name
};
}
const step1Action = () => {
return {
type: 'STEP_1',
payload: []
};
// need to dispatch step2Actions
dispatch(step2Action('Kyle'))
// but we don't have access to the dispatch function to dispatch an action
}
Solution
Thunks - a special kind of action
const step2Action = name => {
return {
type: "STEP_2",
payload: name
};
};
const step1Actions = () => {
return (dispatch) => {
dispatch(step2Action("Kyle"));
};
};
- Thunks are just actions which return a function with dispatch as an argument
- you can use dispatch to send out actions
Adding the Thunk Middleware
import { createStore, applyMiddleware, compose } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './redux/reducers';
export const history = createHistory();
const initialState = {};
const enhancers = [];
const middleware = [thunk, promiseMiddleware()];
const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);
export default createStore(rootReducer, initialState, composedEnhancers);
What do we have now?
- chain actions together with thunks
- have a promise as a payload
- can we combine the two to chain async actions together?
Thunks and Promises
const secondAction = (number) => ({
type: 'TWO',
payload: number + 1,
})
const first = () => {
return (dispatch) => {
const response = dispatch({
type: 'ONE',
payload: Promise.resolve(1),
})
response.then((number) => {
dispatch(secondAction(number))
})
}
We can now take the result of an async action and pass it into another action!
Why is this useful?
- Imagine getting data from an API
- You make one call which contains a list of Ids of a entity
- Then you need to make another call using the ids from the previous call to get more information
Demo - Star Wars API
Lets get a list of characters and their home worlds.
GET swapi.co/api/people
{
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "https://swapi.co/api/planets/1/",
}
]
}
- To get people and their home worlds we need to do two API calls.
- one for getting people
- one for each persons home world
- sound familiar?
- let use thunks + promise to solve this
Demo
Questions?
Fin
Contact me
- kyle@kylepotts.com
- github.com/kylepotts
- @kylepotts on Twitter
Redux Forms And Middleware
By Kyle Potts
Redux Forms And Middleware
- 300