Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Re-energize Your Workflow with React and Redux

Re-energize Your Workflow with React and Redux

Redux helps you build applications with React by making it easier to manage data changes and state in your application. In this talk, Jonathan walks you through building applications with React and Redux using ES6, Babel and Webpack.

Jonathan Kemp

October 22, 2016
Tweet

More Decks by Jonathan Kemp

Other Decks in Programming

Transcript

  1. Scripps Networks HGTV · DIY Network · Food Network ·

    Cooking Channel · Travel Channel www.scrippsnetworksinteractive.com/careers
  2. Bidirectional Data Flow - Results can be unpredictable. - Hard

    to understand - Hard to update and maintain - Difficult to debug
  3. Flux - Applications have three major parts: 1. the dispatcher

    2. the stores 3. the views (React components)
  4. Data Flow - The dispatcher then invokes the callbacks registered

    with the stores, dispatching actions to all stores.
  5. Data Flow - Within their registered callbacks, stores respond to

    whichever actions are relevant to the state they maintain.
  6. Data Flow - The stores then emit a change event

    to alert the controller- views that a change to the data layer has occurred.
  7. Data Flow - Controller-views listen for these events and retrieve

    data from the stores in an event handler. - Views update themselves and all of their descendants.
  8. Benefits of Flux - Makes it easy to reason about

    complex data changes - Opens up possibilities impossible with classic MVC, such as recording and replaying UI state - Easy Testability
  9. Redux 1. Redux avoids the complexity found in Flux 2.

    Functional programming approach (to Flux) 3. Redux is the most popular and well-documented Flux Library.
  10. Redux Overview - State of your app is stored in

    an object tree inside a single store. - Only way to change the state tree is to emit an action, an object describing what happened. - To specify how the actions transform the state tree, you write pure reducers.
  11. Differences from Flux 1. Redux does not have the concept

    of a Dispatcher. 2. Redux assumes you never mutate your data.
  12. Three Principles of Redux 1. Single source of truth 2.

    State is read-only 3. Changes are made with pure functions
  13. Single source of truth 1. The state of your whole

    application is stored in an object tree within a single store. 2. A single state tree also makes it easier to debug or introspect an application.
  14. State is read-only - The only way to mutate the

    state is to emit an action, an object describing what happened. - This ensures that neither the views nor the network callbacks will ever write directly to the state.
  15. Changes are made with pure functions - To specify how

    the state tree is transformed by actions, you write pure reducers.
  16. Side Effects A side effect is a change that is

    not local to the function that caused it.
  17. Pure Functions 1. Avoid globals A. Ensure all its data

    comes from inputs and it never reaches outside itself 2. Don’t modify input 3. Avoid side-effects
  18. Redux Overview - State of your app is in a

    single store. - Only way to change the state tree is to emit an action. - specify how the actions transform the state with pure reducers.
  19. Keep Reducers Pure - Don’t Mutate its arguments - Don’t

    Perform side effects - Don’t Call non-pure functions
  20. function counter(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } let store = createStore(counter); store.subscribe(() => console.log(store.getState()) );
  21. function counter(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } let store = createStore(counter); store.subscribe(() => console.log(store.getState()) );
  22. function counter(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } let store = createStore(counter); store.subscribe(() => console.log(store.getState()) );
  23. Data Flow 1. You call store.dispatch(action). 2. The Redux store

    calls the reducer function you gave it. 3. The Redux store saves the complete state tree returned by the root reducer.
  24. Get started with Redux 1. Can be used with any

    view library, independent of React 2. Babel or module bundler not required 3. Install via npm
  25. Features: I. History tape a. Every calculation gets saved to

    the history tape b. Refer to calculations later or send them right back to the calculator c. Allows you to see everything you have typed
  26. { items: [ { id: 1, expression: "5 * 3",

    result: 15 }, { id: 0, expression: "5 * 2", result: 10 } ], lastResult: 15 }
  27. import { connect } from 'react-redux'; import { addEntry }

    from '../actions/index'; import EntryForm from ‘../components/EntryForm'; const mapStateToProps = (state) => { return { result: state.lastResult } }; const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  28. import { connect } from 'react-redux'; import { addEntry }

    from '../actions/index'; import EntryForm from ‘../components/EntryForm'; const mapDispatchToProps = (dispatch) => { return { submit: (value) => { dispatch(addEntry(value)); } } } const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  29. import { connect } from 'react-redux'; import { addEntry }

    from '../actions/index'; import EntryForm from ‘../components/EntryForm'; const mapDispatchToProps = (dispatch) => { return { submit: (value) => { dispatch(addEntry(value)); } } } const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  30. import { connect } from 'react-redux'; import { addEntry }

    from '../actions/index'; import EntryForm from ‘../components/EntryForm'; const mapDispatchToProps = (dispatch) => { return { submit: (value) => { dispatch(addEntry(value)); } } } const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  31. render() { return ( <form onSubmit={(e) => { e.preventDefault(); this.props.submit(this.input.value);

    this.input.value = ''; }}> <div className="form-group"> <input type="text" ref={node => { this.input = node }} onChange={this.handleChange} /> </div> <button type="submit">Submit</button> </form> ); }
  32. render() { return ( <form onSubmit={(e) => { e.preventDefault(); this.props.submit(this.input.value);

    this.input.value = ''; }}> <div className="form-group"> <input type="text" ref={node => { this.input = node }} onChange={this.handleChange} /> </div> <button type="submit">Submit</button> </form> ); }
  33. /* * action types */ export const ADD_ENTRY = 'ADD_ENTRY';

    /* * action creators */ export function addEntry(text) { return { type: ADD_ENTRY, text }; }
  34. function calculateApp(state, action) { switch (action.type) { case ADD_ENTRY: return

    Object.assign({}, state, { items: [ { id: state.items.reduce((maxId, item) => Math.max(item.id, maxId), -1) + 1, expression: action.text, result: eval(action.text) }, ...state.items ] }) default: return state } }
  35. function calculateApp(state, action) { switch (action.type) { case ADD_ENTRY: return

    Object.assign({}, state, { items: [ { id: state.items.reduce((maxId, item) => Math.max(item.id, maxId), -1) + 1, expression: action.text, result: eval(action.text) }, ...state.items ] }) default: return state } }
  36. import React from 'react'; import { render } from 'react-dom';

    import { Provider } from 'react-redux'; import { createStore } from 'redux'; import calculateApp from './reducers/index'; import App from './components/App'; const store = createStore(calculateApp); render( <Provider store={store}> <App /> </Provider>, document.getElementById('app') );
  37. Data Flow 1. call store.dispatch(action). 2. The Redux store calls

    the reducer function. 3. The Redux store saves the state tree returned by the root reducer.
  38. import { connect } from 'react-redux'; import { removeEntry }

    from '../actions'; import EntryList from '../components/EntryList'; const mapStateToProps = (state) => { return { items: state.items } }; const PushEntryList = connect( mapStateToProps, mapDispatchToProps )(EntryList);
  39. import { connect } from 'react-redux'; import { removeEntry }

    from '../actions'; import EntryList from '../components/EntryList'; const mapDispatchToProps = (dispatch) => { return { remove: (index) => { dispatch(removeEntry(index)); } } } const PushEntryList = connect( mapStateToProps, mapDispatchToProps )(EntryList);
  40. import { connect } from 'react-redux'; import { removeEntry }

    from '../actions'; import EntryList from '../components/EntryList'; const mapDispatchToProps = (dispatch) => { return { remove: (index) => { dispatch(removeEntry(index)); } } } const PushEntryList = connect( mapStateToProps, mapDispatchToProps )(EntryList);
  41. import { connect } from 'react-redux'; import { removeEntry }

    from '../actions'; import EntryList from '../components/EntryList'; const mapDispatchToProps = (dispatch) => { return { remove: (index) => { dispatch(removeEntry(index)); } } } const PushEntryList = connect( mapStateToProps, mapDispatchToProps )(EntryList);
  42. render() { return ( <li className="list-group-item"> <div>{this.props.result}</div> <div>{this.props.expression}</div> <button onClick={()

    => { PubSub.publish('entry', this.props.result); }}>Use Result</button> <button onClick={() => { PubSub.publish('entry', this.props.expression); }}>Use Expression</button> <button onClick={this.props.remove}> Remove </button> </li> ) }
  43. render() { return ( <li className="list-group-item"> <div>{this.props.result}</div> <div>{this.props.expression}</div> <button onClick={()

    => { PubSub.publish('entry', this.props.result); }}>Use Result</button> <button onClick={() => { PubSub.publish('entry', this.props.expression); }}>Use Expression</button> <button onClick={this.props.remove}> Remove </button> </li> ) }
  44. /* * action types */ export const REMOVE_ENTRY = 'REMOVE_ENTRY';

    /* * action creators */ export function removeEntry(index) { return { type: REMOVE_ENTRY, index }; }
  45. function calculateApp(state, action) { switch (action.type) { case REMOVE_ENTRY: return

    Object.assign({}, state, { items: [ ...state.items.slice(0, action.index), ...state.items.slice(action.index + 1) ] }) default: return state } }
  46. function calculateApp(state, action) { switch (action.type) { case REMOVE_ENTRY: return

    Object.assign({}, state, { items: [ ...state.items.slice(0, action.index), ...state.items.slice(action.index + 1) ] }) default: return state } }
  47. import { connect } from 'react-redux'; import { updateResult }

    from '../actions/index'; import EntryForm from '../components/EntryForm'; const mapDispatchToProps = (dispatch) => { return { change: (value) => { dispatch(updateResult(value)); } } } const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  48. import { connect } from 'react-redux'; import { updateResult }

    from '../actions/index'; import EntryForm from '../components/EntryForm'; const mapDispatchToProps = (dispatch) => { return { change: (value) => { dispatch(updateResult(value)); } } } const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  49. import { connect } from 'react-redux'; import { updateResult }

    from '../actions/index'; import EntryForm from '../components/EntryForm'; const mapDispatchToProps = (dispatch) => { return { change: (value) => { dispatch(updateResult(value)); } } } const AddEntry = connect( mapStateToProps, mapDispatchToProps )(EntryForm);
  50. handleChange() { let value = this.input.value !== '' ? eval(this.input.value)

    : 0; this.props.change(value); }, render() { return ( <div className="form-group"> <input type="text" ref={node => { this.input = node }} onChange={this.handleChange} /> </div> ); }
  51. handleChange() { let value = this.input.value !== '' ? eval(this.input.value)

    : 0; this.props.change(value); }, render() { return ( <div className="form-group"> <input type="text" ref={node => { this.input = node }} onChange={this.handleChange} /> </div> ); }
  52. handleChange() { let value = this.input.value !== '' ? eval(this.input.value)

    : 0; this.props.change(value); }, render() { return ( <div className="form-group"> <input type="text" ref={node => { this.input = node }} onChange={this.handleChange} /> </div> ); }
  53. /* * action types */ export const UPDATE_RESULT = 'UPDATE_RESULT';

    /* * action creators */ export function updateResult(result) { return { type: UPDATE_RESULT, result }; }
  54. function calculateApp(state, action) { switch (action.type) { case UPDATE_RESULT: return

    Object.assign({}, state, { lastResult: action.result }) default: return state } }
  55. function calculateApp(state, action) { switch (action.type) { case UPDATE_RESULT: return

    Object.assign({}, state, { lastResult: action.result }) default: return state } }
  56. const localStore = store => next => action => {

    const result = next(action); localStorage.setItem('recalculator', JSON.stringify(store.getState())); return result; }; const data = JSON.parse(localStorage.getItem('recalculator')) || { items: [], lastResult: 0 }, store = createStore( calculateApp, data, compose( applyMiddleware(localStore), window.devToolsExtension ? window.devToolsExtension() : f => f ) );
  57. const localStore = store => next => action => {

    const result = next(action); localStorage.setItem('recalculator', JSON.stringify(store.getState())); return result; }; const data = JSON.parse(localStorage.getItem('recalculator')) || { items: [], lastResult: 0 }, store = createStore( calculateApp, data, compose( applyMiddleware(localStore), window.devToolsExtension ? window.devToolsExtension() : f => f ) );
  58. const localStore = store => next => action => {

    const result = next(action); localStorage.setItem('recalculator', JSON.stringify(store.getState())); return result; }; const data = JSON.parse(localStorage.getItem('recalculator')) || { items: [], lastResult: 0 }, store = createStore( calculateApp, data, compose( applyMiddleware(localStore), window.devToolsExtension ? window.devToolsExtension() : f => f ) );
  59. const localStore = store => next => action => {

    const result = next(action); localStorage.setItem('recalculator', JSON.stringify(store.getState())); return result; }; const data = JSON.parse(localStorage.getItem('recalculator')) || { items: [], lastResult: 0 }, store = createStore( calculateApp, data, compose( applyMiddleware(localStore), window.devToolsExtension ? window.devToolsExtension() : f => f ) );
  60. Redux Overview - State of your app is in a

    single store. - Only way to change the state tree is to emit an action. - specify how the actions transform the state with pure reducers.
  61. FLux - Unidirectional data flow - Models should be plain

    objects - Single source of truth for data - Makes it easy to reason about complex data changes
  62. Redux - Helps you write applications that behave consistently, run

    in different environments (client, server, and native), and are easy to test - Good developer experience - implement logging, hot reloading, time travel, universal apps, record and replay - Can use Redux together with React, or with any other view library.
  63. Resources - Getting Started with Redux
 https://egghead.io/courses/getting-started-with-redux - Building Applications

    with Idiomatic Redux
 https://egghead.io/courses/building-react-applications-with- idiomatic-redux