Day 2, Segment 2, Redux

In notebook:
FrontEndMasters React Intro 2
Created at:
2016-12-27
Updated:
2016-12-27
Tags:
libraries React JavaScript

Redux

It's a very heavy library in terms of having to refactor your code. It's overkill for most apps. He rarely uses it. 
He brings in Redux, when the state becomes too complex too manage. 
Even Dan Abromov admits that Redux is not  meant for most apps. 
Start with React, then refactor later if necessary to bring in Redux. Refactoring is not that difficult. 

What Redux is

It's a simple but hard library. You can implement Redux in 80 LOC. My note: I certainly did, see the Redux workshop. 

"It's a central repository of all of your state"

It becomes quickly impossible to manage, if your state is spread across multiple components. For example orchestrating animation across several components. Almost impossible, since components cannot really talk to each other.

You externalise all of your state to [Redux/Flux/...]. 12:00
Now it's very easy to update the state across all of your app. 
The disadvantage is that it will be harder to read your code: you don't exactly know where the props etc come from. 
It's still a very good pattern, and can still be easy to debug, as the data lives in one centralised place.

It works with any library (Angular, etc).

Where does it come from?

Animations were hard, when Facebook first launched React. Also messy, to always having to push the data up to the common ancestor component. Imagine having to go up seven layers, not just one. Called the data tunnelling problem. 
Apply the one-way data flow of React into state management
(Q&A: Netflix uses Redux at some parts, but not everywhere)
First they released Flux, as a one-way data flow idea. It was just an idea at first, not a library, and of course some people built libraries based on the Flux pattern.
The Dan Abromov (one of the core contributors to React as well) come up with hot reloadable Flux container for a conference talk. This was the base for Redux. With Flux you have multiple stores, with Redux you only have one. 

What makes Redux?

Mobacks is even more complex than the Redux library. 
You have a datastore that you cannot directly modify, but through reducers (functional programming concept).
Arrays have ​Array.reduce​ which is one type of reducer. A reducer takes in an action and a function and creates a new state. That's it. Most reducers you will use are very simple.
Important is that you should not create any side-effect with these reducers. The original state does not get modified, but you always create a new object and return that. 

Q&A: Redux does not do any compression or data optimisation.
(my note: not primitive values are passed as reference in JavaScript, so that is data compression)

Redux is just a big object basically that the reducers then take as input and return a new object.

Root reducers

You always call the root reducer first. You could put all your logic into this reducer, but in most cases you dispatch to other reducers.

Create the root reducer 

Create the reducers.js file. Notice the lowercase "r", as it is not a React component.
  // ****   reducers.js ****

import { SET_SEARCH_TERM } from './actions'

// 1. ++++ add the default state
const DEFAULT_STATE = {
  searchTerm: 'something random'
}

// 2. ++++ create the root reducer
// have to provide a base state
// ES6 default parameter, same as 
// state = state || DEFAULT_STATE
const rootReducer = (state = DEFAULT_STATE, action) => {
  // the base logic of Redux
  // every action must have a type
  // type decides to which reducer 
  // you route to
  switch (action.type) {
      // always have a default action
    default:
    // state is the base for Redux
      return state
  }
}

export default rootReducer

​combineReducers

You can use it to replace rootReducer and dispatch to the other reducers. 

Q&A: Data normalisation with Redux? Brian doesn't have an opinion on it, but there are lots of articles written on it...

The Store

Create store.js:
  // ****   store.js    ****

import { createStore } from 'redux'
import rootReducer from './reducers'

const store = createStore(rootReducer)

export default store
Talks about ES6 modules. The exports are not named exports, so you can do:
  export default = createStore(rootReducer)
And also:
  import blah from './reducers'
And also:
  import { createStore as foo } from 'redux'
Back:
Later, he'll explain Redux middlewares. 

Modify the Store

We have to dispatch an action with action creators. It's the store that informs React if the state has changed. The loop is very tight, even though it's seven steps to modify the state. But it's very predictable.

Create actions.js:
  // ****   actions.js    ****

export const SET_SEARCH_TERM = 'SET_SEARCH_TERM'
For now, actions has no logic. The names of the actions are now in one central place. Great for maintainability. 
Then, create  actionCreators.js :
  // ****     actionCreators.js   ****

// import named export
import { SET_SEARCH_TERM } from './actions'

export function setSearchTerm (searchTerm) {
  return { type: SET_SEARCH_TERM, searchTerm: searchTerm }
}
Again, you keep the actions in a separate, central place so your UI can use it without knowing the implementation details. 

The above two files, actions.js and actionCreators.js are very important for long term maintainability. It's useful to separate them out to pieces.

Prepare Reducers for the newly defined actions

  // ****   reducers.js   ****

// 1. ++++
import { SET_SEARCH_TERM } from './actions'

const DEFAULT_STATE = {
  searchTerm: 'something random'
}

// 3. ++++
const setSearchTerm = (state, action) => {
  const newState = {}
  Object.assign(newState, state, {searchTerm: action.searchTerm})
  return newState
}

const rootReducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    // 2. ++++
    case SET_SEARCH_TERM:
      // dispatch to another action
      return setSearchTerm(state, action)
    default:
      return state
  }
}

export default rootReducer

The anatomy of a reducer

Most reducers follow the same pattern as the ​setSearchTerm​ reducers above (using ​Object.assign(...)​).
You should never modify the old state. Redux does a shallow compare of the old and new state and will not run the update (update React) if they're the same. 
You can also use the Object spread operator (ES7) in the place of ​Object.assign​.

Connect the UI to use the Redux state

  // ****  ClientApp.js    ****
import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter, Match } from 'react-router'
// 1. ++++ import the libraries
// react-redux and store
import { Provider } from 'react-redux'
import store from './store'
import Landing from './Landing'
import Search from './Search'
import Details from './Details'
import preload from '../public/data.json'
import '../public/normalize.css'
import '../public/style.css'

const App = React.createClass({
  render () {
    return (
      <BrowserRouter>
      // 2. ++++ add the provider
      // and define the store
        <Provider store={store}>
          <div className='app'>
            <Match exactly pattern='/' component={Landing} />
            <Match
              pattern='/search'
              component={(props) => <Search shows={preload.shows} {...props} />}
            />
            <Match
              pattern='/details/:id'
              component={(props) => {
                ...
              }}
            />
          </div>
        </Provider>
      </BrowserRouter>
    )
  }
})

render(<App />, document.getElementById('app'))
react-redux is a very small utility. It connects your app to Redux.

Update Landing.js. 
  // ****   Landing.js    ****

import React from 'react'
// 1. ++++
import { connect } from 'react-redux'
import { Link } from 'react-router'
const { string } = React.PropTypes

// 4. ++++ finally document the proptypes
const Landing = React.createClass({
  propTypes: {
    searchTerm: string
  },
  render () {
    return (
      <div className='landing'>
        <h1>svideo</h1>
        // 5. ++++ use the searchTerm prop
        <input value={this.props.searchTerm} type='text' placeholder='Search' />
        <Link to='/search'>or Browse All</Link>
      </div>
    )
  }
})

// 2. ++++
const mapStateToProps = (state) => {
  // just return the searchTerm from the 
  // entire Redux store
  return {
    searchTerm: state.searchTerm
  }
}

// 3. ++++ then the magic here
// connect the returned state to props
// connect returns a function that we call immediately
export default connect(mapStateToProps)(Landing)
Now we have connected Redux to Landing.