React
& Redux

HSR CAS Frontend Engineering 2018


slides.com/dmodalek/hsr-cas-react-redux-2018

Content

  1. Why Redux?
     
  2. Redux Basics
     
  3. Redux Advanced
     
  4. Async Flow
     
  5. Data Handling

About Me

Dominic Modalek

Frontend Architect @ Namics AG

 

Current React & Redux Projects:

Migros MeinCoach, Pathé.ch and Sunrise.ch

Why Redux?

React without Redux

  • Concepts State, Props and Lifecycle Methods
     
  • Component Communication via parent component and Props
     
  • App State is simple and matches the Component structure
     

→ Example "1. React without Redux"

https://github.com/dmodalek/hsr-react-redux

https://stackblitz.com/edit/hsr-1-react-without-redux

Component Communication without Redux

App State Management

without Redux

  • A lot of the time your app's state tree could be considerably different than the UI tree
     
  • Leaf components need to access state that their parents don't need
     
  • Many components need access to the same data
     
  • React's state management is enough for UI state that only this one component needs

React vs. Redux

What the author of Redux says

Redux Basics

Redux Flow

Redux Flow

  1. Store
     
  2. Actions
     
  3. Reducer

Redux Concepts

Single source of truth

The state of your whole application is stored in a single object.

 

1. Store

console.log(store.getState())

// Returns:
{
  selectedMovies: [1, 3]
  movies: {
    1: {
      title: 'A Hologram for the King'
    },
    2: {
      title: 'How to Be Single'
    },
    3: {
      title: 'The First Avenger - Civil War'
    }
  }
}

The State in the Store is read-only

The only way to change the state is to emit an action, an object describing what happened.

2. Actions

store.dispatch({
  type: 'ADD_MOVIE',
  title: 'X-Men: Apocalypse'
})

Actions describe the fact that something happened, but don't specify how the application's state changes in response. This is the job of a reducer.

3. Reducer

function movieReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_MOVIE:
      return Object.assign({}, state, {
        movies: [
          ...state.movies,
          {
            title: action.title
          }
        ]
      })    
    default:
      return state
  }
}

Praxisübung

Redux Advanced

Async

Action Creator is a function that returns an Action

 

// Without Action Creator
store.dispatch({
  type: 'INCREMENT',
  count: 1
})
// With Action Creator
const increment = (count) => ({
  type: INCREMENT,
  count
})

store.dispatch(increment(1))

Async

Redux Thunk middleware

allows to dispatch Functions that have access to state and dispatch

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

function incrementAsync() {
  return (dispatch, getState) => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 1000);
  };
}
// Thunk middleware provides dispatch and getState
  
return function (dispatch, getState) {

  // Normal, sync action
  dispatch(requestPosts(subreddit))

  // The function called by the thunk middleware can return a value,
  // that is passed on as the return value of the dispatch method.
  // In this case, we return a promise to wait for.

  return fetch(`http://www.reddit.com/r/${subreddit}.json`)
  .then(response => response.json())
  .then((json) => {

    // Update the app state with the results of the API call.
    dispatch(receivePosts(subreddit, json))
  })
}

Async

Praxisübung

Redux embraces the idea of separating presentational and container components. They are also known as Smart and Dumb components.

 

Container components connect to the store and provide data for the presentational components

Presentational &

Container Components

Presentational &

Container Components

React-Redux Bindings

Redux has no relation to React. You can write Redux apps with React, Angular, Ember, jQuery, or Vanilla JavaScript.

 

React bindings are not included in Redux by default. You need to install them explicitly.

 

https://github.com/reactjs/react-redux

React-Redux Bindings

Provides the store functions to all components automatically via a Provider component:

 

 

 

The connect() function wraps a presentational component and provides it with the store data it needs

 

 

<Provider store={storeInstance}>
   <App />
</Provider>
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(HomepageView)

Selectors

Selectors are used on Container Components to get data from the Store and provide it to the Components.

 

For example getMovies() or getUpcomingMovies()

ES 6 Functions

Arrays

.map()

.filter()

.reduce()

 

.find()

.findIndex()

.includes()

Objects

Object.keys()

(not ES6 but useful)

Praxisübung

→ 4. Redux Movies Part I

 

https://github.com/dmodalek/hsr-react-redux

A higher-order component (HOC) is an advanced technique in React for reusing component logic.

 

A higher-order component is a function that takes a component and returns a new component.

 

 

 

Example: the Redux connect() function is a HOC that connects the wrapped Component to the Redux store.

 

 

Higher Order Components

const EnhancedComponent = higherOrderComponent(WrappedComponent);

Higher Order Components

// A HOC that logs props
const withLogProps = (WrappedComponent) => {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// Use
const MyEnhancedComponent = withLogProps(MyComponent);

Higher Order Components

Recompose is a A React utility belt for function components and higher-order components — similar to what Lodash is for JavaScript.

 

https://github.com/acdlite/recompose

// Optimized version of same component, using shallow comparison of props
// Same effect as extending React.PureComponent
const OptimizedComponent = pure(ExpensiveComponent)

// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedComponent = onlyUpdateForKeys(['propA', 'propB'])(ExpensiveComponent)

Async Flow

Promises

var p = new Promise(function(resolve, reject) {
    if (true) {
    resolve('Success!');
  } else {
    reject('Failure!'); 
  }
});

p.then(function(response) { 
    console.log('resolved:', response)
}).catch(function(error) {
    console.log('rejected', error)
})

Promises

Resolve, Reject

  • Call resolve() within a Promise to mark it as fulfilled and optionally pass in a return value.

  • Call reject() within a Promise to mark it as rejected and optionally pass in a return error message.

 

Then, Catch

  • then() is executed when the previous Promise was resolved

  • catch() is executed when the previous Promise was rejected

Promises

Details

 

You can also use then(success, error) to handle rejections by using the second argument for then().
 

Using .catch() may be better readable — and using then(success, error) doesn't handle errors emitted by the success handler of the same then(success, error).

 

Another then() or catch() is needed to catch that error

Promise

Chain

doPromiseWork()
    .then(doWork)
    .then(doError)
    .then(doWork) // this will be skipped
    .then(doWork, errorHandler)
    .then(doWork)  // we resume here
    .then(doError)
    .then(doWork) // this will be skipped
    .catch(errorHandler)
    .then(doWork) // we resume here

 

Exceptions thrown inside of then() and catch() are converted to a rejection and reject the promise automatically. It's the same as calling reject() manually.
 

It therefore jumps to the next rejection handler (errorHandler) and skips over any potential success handlers.
 

After that, the code resumes with the next function then(doWork) after the error handler

 

  constructor (props) {
    super(props)
    props.doLoadTickets(props.site, props.sessionID)
    .catch((error) => {
      props.addNotification('error', error.message, 'tickets')
    })
    .then(() => {
      return props.doLoadMember(props.site, props.sessionID)
    })
    .then(() => {
      return Promise.all([
        props.doLoadTickets(props.site, props.sessionID),
        props.doGetItems(props.sessionID)
      ])
    })
    .then(() => {
      return props.doAddLoyalityTicketsToSelection()
    })
    .catch(() => {
      // do nothing
      // - it is ok if there is no member
    })
    .then(() => {
      return props.doGetOrder(props.sessionID)
    })
    .then(() => {
      return props.doRehydrateOrder()
    })
    .catch(() => {
      // do nothing
    })
  }

Async Functions

Async functions are a new feature
scheduled to become a part of
ES7.
 

They build on top of Promises. Write async code that looks like synchronous code.

.

// Async Await
async componentDidMount() {
    try {
      await this.props.doLoadMovies();
      await this.props.doLoadMovieDetails();
    } catch(e) {
      // whoops!
      console.log("Could not load data...");
      this.setState({ error: true });
    }  
}
// Promises 
componentDidMount() {
    this.props
      .doLoadMovies()
      .then(() => {
        this.props.doLoadMovieDetails();
      })
      .catch(() => {
        // whoops!
        console.log("Could not load data...");
        this.setState({ error: true });
      });
}

Praxisübung

→ 4. Redux Movies Part II

 

https://github.com/dmodalek/hsr-react-redux

Data Handling

Mutable

vs.

Immutable Data

"If you go to this page, click on that button, then go back to the home page, grab a coffee, go to this page and click twice here, something weird happens"

Immutable Data

Immutable data cannot be changed once created.
 

This leads to much simpler application development and change detection techniques.

 

+ Easier to Test

+ Performance

+ Predictability

+ Undo & Redo aka Time Travel

Mutable Data

// create object one
let one = { completed: false }

// create a reference
var two = one;

// they now share the same reference
console.log(two === one) // true

// therefore changes done in one are reflected in the other
one.completed = true
console.log(two) // true

Objects in JavaScript are mutable and passed by reference.

Using an immutable approach would allow us to only compare the reference to check for Object equality.

Immutable JS

Immutable JS

Immutable JS is a helper library developed by Facebook that provides tools to create and manipulate immutable data structures.

 

It enforces the functional approach by forbidding objects modifications. With Immutable, when we want to update an object, we actually create another one with the modifications, and leave the original one as is.

Immutable JS

// Immutable.js

// Example Immutable Object
var immutableObj = Immutable.Map({
  one:'entry'
});

// Create a Reference
var immutableCopy = immutableObj;

// Still allows comparing references
console.log(immutableCopy === immutableObj); // true

// Set a value that is already the same is still the same reference
var immutableCopy = immutableObj.set('one', 'entry');

console.log(immutableCopy === immutableObj); // true

// Set a new value is a change and creates a new Object Reference
var immutableCopy = immutableObj.set('one', 'changed');

console.log(immutableCopy === immutableObj); // false

Automatically creates new Arrays or Objects when changing values.

How to change Arrays and Objects without mutating them?

...and without using a library like Immutable JS

Immutable Arrays methods

// Copy
return [...list]

// Add Item
// - could also use .concat()
// - don't use .push() because it mutates the original Array
return [...list, item]

// Remove Item
// - don't use .splice() because it mutates the original Array
return [
  ...list.slice(0, index),
  ...list.slice(index + 1)
]

// Change Item
return [
  ...list.slice(0, index),
  ...item,
  ...list.slice(index + 1)
]

Immutable Object methods

/**
 * Copy
 */

// ES6 Object.assign()
Object.assign({}, myObject)

// ES7 Object Spread Operator
return {
    ...myObject
}


/**
 * Add Prop
 */

// ES6 Object.assign()
Object.assign({}, myObject, { done: true })

// ES7 Object Spread Operator
return {
    ...myObject,
    whatever: true
}
/**
 * Remove Prop
 */
// ES6 Object.assign
Object.assign({}, myObject, { done: undefined })

// ES7 Object Spread Operator
return {
    ...myObject,
    completed: undefined
}


/**
 * Change Prop
 */

// ES6 Object.assign
Object.assign({}, myObject, { done: true })

// ES7 Object Spread Operator
return {
    ...myObject,
    completed: true
}

Normalize Data

// Nested data

[{
  id: 1,
  title: 'Some Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}, {
  id: 2,
  title: 'Other Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}]

Updating nested immutable data is hard. Store it as normalized as possible.

// Normalized data

articleIDs: [1, 2],
articles: {
    articles: {
      1: {
        id: 1,
        title: 'Some Article',
        author: 1
      },
      2: {
        id: 2,
        title: 'Other Article',
        author: 1
      }
    },
    users: {
      1: {
        id: 1,
        name: 'Dan'
      }
    }
  }
}

Normalized Data & Selectors

// Selector

export const getCategories = (state) => {
  return state.tickets
  .filter((ticket) => {
    return isMainTicketCategory(ticket.id)
  })
}

Use Selectors to build the data structure you need in your components

Praxisübung

→ 4. Redux Movies Part III

 

https://github.com/dmodalek/hsr-react-redux

Recap

  • Only use Redux when you need it
     
  • Redux architecture revolves around a strict unidirectional data flow.
     
  • Redux provides store.getState(), store.dispatch() and store.subscribe()
     
  • Use Container Components to provide data to Presentational Components
     
  • Reducers in Redux are Pure Functions
     
  • Use immutable methods to change Redux state
     
  • Store data normalized and use Selectors to get data for the components

Ressources

Alternatives

React JS → Vue JS
https://github.com/vuejs/vue

 

Redux → MobX
https://github.com/mobxjs/mobx


Redux Thunk → Redux Saga
https://github.com/redux-saga/redux-saga

Project Architecture

→ Migros MeinCoach

One React App with a Router

 

→ Pathé

Multiple React Apps

Merci 🎉