React
& Redux

HSR CAS Frontend Engineering 2016


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

Content

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

About Me

Dominic Modalek

Senior Frontend Engineer @ Namics AG

 

React & Redux Projects:

Sunrise.ch und Pathé.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

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 may need to access the same state and display it in different ways

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'
    }
  }
}

State is read-only

The only way to change the state in the store 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

→ 3. Redux Counter Async

 

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

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 higher order component <Provider store={store}>.

 

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

 

 

Selectors

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

 

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

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().
 

But 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(deWork)  // 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 previous capabilities made available by ES6 (promises), letting you write async code as though it were synchronous.

// Async Await
async function doAsyncOp () {
    var val = await asyncFunction();
    val = await asyncFunction(val);
    val = await asyncFunction(val);
    return await asyncFunction(val);
};
// Promises 
function doAsyncOp () {
    return asyncFunction().then(function(val) {
        return asyncFunction(val);
    }).then(function(val) {
        return asyncFunction(val);
    }).then(function(val) {
        return asyncFunction(val);
    });
};

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 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.
     
  • Reducers in Redux are Pure Functions
     
  • Redux provides store.getState(), store.dispatch() and store.subscribe()
     
  • Use Container Components to provide data to Presentational Components
     
  • Use immutable methods to change Redux state
     
  • Store data normalized and use Selectors to get data for the components

Ressources

Merci 🎉