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

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
Made with Slides.com