Day 2, Segment 3

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

The search field still doesn't work. We will fix this next.
  // ****   Landing.js    ****

import React from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
// 2. ++++ import our action
import { setSearchTerm } from './actionCreators'
const { string, func, object } = React.PropTypes

const Landing = React.createClass({
  propTypes: {
    searchTerm: string,
    // 4. ++++ declare the dispatch
    dispatch: func
  },
  // 1. ++++
  handleSearchTermChange (event) {
    this.props.dispatch(setSearchTerm(event.target.value))
  },
  render () {
    return (
      <div className='landing'>
        <h1>svideo</h1>
        // 3. ++++ add the onChange handler
          <input onChange={this.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='Search' />
        <Link to='/search'>or Browse All</Link>
      </div>
    )
  }
})

const mapStateToProps = (state) => {
  return {
    searchTerm: state.searchTerm
  }
}

export default connect(mapStateToProps)(Landing)
The ​connect​ from React-redux library automatically adds a dispatch​ function that lets you dispatch actions to Redux. 

Q&A: it's recommended to always declare and pull out the just the minimum of what you need from the Redux state, as in mapStateToProps​ above. Otherwise it can get very messy. 

With the above pattern it's still props down, actions up.

Dispatch

You create and action and dispatches it to the rootReducer.

Overview of the Redux cycle

  1. type in the search field
  2. fires the onChange in the input
  3. ​handleSearchTermChange​ will call our actionCreator
  4. that creates the properly formatted action definition ({type:[correct_name], searchTerm:searchTerm} (the type name is defined in action.js)
  5. then dispatches this action definition to Redux rootReducer
  6. then rootReducer creates (through the appropriate reducer) the new state with the new searchTerm. 
  7. It gets back to the store. 
  8. Redux has a new store and the ​connect​ method above is subscribed to this store. And it calls forceUpdate in React 
  9. that re-renders the app

Refactor the above using ​mapDispaptchToProps

You create a mapDispatchToProps​ function, that you pass to ​connect​, so now dispatchSetSearchTerm​ injected as a prop to Landing. 
  // ****   Landing.js    ****

import React from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import { setSearchTerm } from './actionCreators'
const { string, func, object } = React.PropTypes

const Landing = React.createClass({
  contextTypes: {
    router: object
  },
  propTypes: {
    searchTerm: string,
    dispatchSetSearchTerm: func
  },
  handleSearchTermChange (event) {
    // 3. ++++
    this.props.dispatchSetSearchTerm(event.target.value)
  },
  render () {
    return (
      <div className='landing'>
        <h1>svideo</h1>
          <input onChange={this.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='Search' />
        <Link to='/search'>or Browse All</Link>
      </div>
    )
  }
})

const mapStateToProps = (state) => {
  return {
    searchTerm: state.searchTerm
  }
}

// 1. ++++
const mapDispatchToProps = (dispatch) => {
  return {
    dispatchSetSearchTerm (searchTerm) {
      dispatch(setSearchTerm(searchTerm))
    }
  }
}

// 2. ++++ map it here
// now dispatchSetSearchTerm is injected as a property
export default connect(mapStateToProps, mapDispatchToProps)(Landing)
16:00
The advantage of this pattern is that it's less dependent on Redux, since you're not using ​dispatch​ directly. Now you could use Landing outside of Redux. 
This is more common in real life.

Q&A: Keep the old states?

You would store it as a separate value in Redux. (newSearchTerm and oldSearchTerm for example).

Navigating the browser to the search results

The easiest is just adding a form:
  // ****   Landing.js    ****

import ...
const { string, func, object } = React.PropTypes

const Landing = React.createClass({
  // declare the context types
  // see explanation below
  contextTypes: {
    router: object
  },
  propTypes: {..},
  handleSearchTermChange (event) {..},
  // 2. ++++ add the handler
  handleSearchSubmit (event) {
    // prevent the form from submitting
    event.preventDefault()
    // want to navigate search page
    // 4. ++++ then navigate with React router
    this.context.router.transitionTo('/search')
  },
  render () {
    return (
      <div className='landing'>
        <h1>svideo</h1>
        // 1. ++++ add the form
        <form onSubmit={this.handleSearchSubmit}>
          <input onChange={this.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='Search' />
        </form>
        <Link to='/search'>or Browse All</Link>
      </div>
    )
  }
})

const mapStateToProps = (state) => {..}

const mapDispatchToProps = (dispatch) => {..}

export default connect(mapStateToProps, mapDispatchToProps)(Landing)

​context

The third type after props and state. This is much less used, and much more dangerous. 
You rarely use it directly, but usually through libraries. 
Context is available everywhere in your React app. It's a global for your app. This is why it can be dangerous, as you can easily loose track of what's coming from where (when debugging).

contextTypes

It's very hard to read or modify context. You have to create and declare ​contextTypes​ otherwise it doesn't show up in contexts. 

BrowserRouter actually uses context internally. 

Navigate programatically with React Router (v4)

The above snippet: ​this.context.router.transitionTo('/search')
And of course all the concept about context, as just explained above. 

So now, we do get sent to the search page, but not getting the filtered list yet.
branch v2-20

Q&A: Can you do routing in Redux? There's a library called Redux-Router, maintained by the same people as React-Router. 

Make Search read from Redux

29:00
  // ****   Search.js   ****

import React from 'react'
// 1. ++++ import connect
import { connect } from 'react-redux'
import ShowCard from './ShowCard'
import Header from './Header'
const { arrayOf, shape, string } = React.PropTypes

const Search = React.createClass({
  propTypes: {
    shows: arrayOf(shape({
      title: string,
      description: string
    })),
    // 2. ++++ add searchTerm
    searchTerm: string
  },
  // 5. ---- remove these
  // no longer handled by React
  // getInitialState () {
  //   return {
  //     searchTerm: ''
  //   }
  // },
  // handleSearchTermChange (event) {
  //   this.setState({searchTerm: event.target.value})
  // },
  render () {
    return (
      <div className='search'>
      // 3. ---- remove the old header
      // since it's not reading
      // from redux
        // <Header
        //   showSearch
        //   searchTerm={this.state.searchTerm}
        //   handleSearchTermChange={this.handleSearchTermChange}
        // />
        // 4. ++++ the new header
        <Header showSearch />
        <div>
          {this.props.shows
          // 6. ++-- read the searchTerm from props
          // this.props.searchTerm.toUpperCase()
            .filter((show) => `${show.title} ${show.description}`.toUpperCase().indexOf(this.props.searchTerm.toUpperCase()) >= 0)
            .map((show) => {
              return (
                <ShowCard key={show.imdbID} {...show} />
              )
            })}
        </div>
      </div>
    )
  }
})

// 7. ++++
// add the map
// pull out the searchTerm subset
const mapStateToProps = (state) => {
  return {
    searchTerm: state.searchTerm
  }
}

// 8. ++++ connect
export default connect(mapStateToProps)(Search)

Now, Search is correct, but

Update Header to read from Redux

  import React from 'react'
// 1. ++++
import { connect } from 'react-redux'
// 2. ++++
import { setSearchTerm } from './actionCreators'
import { Link } from 'react-router'

class Header extends React.Component {
  // 6. ++++
  // make sure `this` is pointing to the 
  // right context in
  // handleSearchTermChange
  constructor (props) {
    // call my parent constructor with this
    super(props)

    this.handleSearchTermChange = this.handleSearchTermChange.bind(this)
  }
  // 4. ++++ add the handler
  handleSearchTermChange (event) {
    // see step 6. to make sure this is pointing to 
    // the right context
    this.props.dispatch(setSearchTerm(event.target.value))
  }
  render () {
    let utilSpace
    if (this.props.showSearch) {
      // 5. ++--
      utilSpace = <input onChange={this.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='Search' />
    } else {
      utilSpace = (
        <h2>
          <Link to='/search'>
            Back
          </Link>
        </h2>
      )
    }
    return (
      <header>
        <h1>
          <Link to='/'>
            svideo
          </Link>
        </h1>
        {utilSpace}
      </header>
    )
  }
}

const { func, bool, string } = React.PropTypes
Header.propTypes = {
  // 3. ++++ proptype for dispatch
  dispatch: func,
  showSearch: bool,
  searchTerm: string
}

// 7. ++++ create the subset
const mapStateToProps = (state) => {
  return {
    searchTerm: state.searchTerm
  }
}

// 8. ++++ connect
export default connect(mapStateToProps)(Header)
Note : in ES6 classes, there's no "," between methods. 

Q&A: Migrating from an existing ES5 app?

React does not need to own your entire app. You can start with just the leaf nodes. Then start replacing more and more components. Maybe marry Backbone and React together. 

Branch v2-21

Now the app is complete. We will migrate the AJAX call to Redux as well.
45:00