Day 2, Segment 1

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

Today will have more fun things...

Data in React

Will create the details page.
  // **** Details.js  ****

import React from 'react'

const Details = React.createClass({
  render () {
    return (
      <div className='details'>
        <pre><code>
        // just dump out the data
          {JSON.stringify(this.props, null, 4)}
        </code></pre>
      </div>
    )
  }
})

export default Details
Add the new route to ClientApp.js
  // **** ClientApp.js  ****

import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter, Match } from 'react-router'
import Landing from './Landing'
import Search from './Search'
// 1. ++++
import Details from './Details'
import '../public/normalize.css'
import '../public/style.css'

const App = React.createClass({
  render () {
    return (
      <BrowserRouter>
        <div className='app'>
          <Match exactly pattern='/' component={Landing} />
          <Match
            pattern='/search'
            component={(props) => <Search shows={preload.shows} {...props} />}
          />
          // 2. ++++
          // :id → it's a parameter
          <Match
            pattern='/details/:id'
            component={Details}
          />
        </div>
      </BrowserRouter>
    )
  }
})

render(<App />, document.getElementById('app'))
Make sure: ​<script src="/public/bundle.js"></script>​ has the / in front.

He sees in the details that ​params.id​ has a unique id that used for the url. The router also gives a lot of extra useful info (in the dump, see above). ​pathname​, ​location​, ​pattern​, ​location​.

Using the shows JSON data in two places

In the workshop, it's just a json data file, but let's assume, that it's coming from the server, so you don't want to import it twice. We want to pass the data to a common ancestor component.

Let's refactor clientapp.js for this.
  // **** ClientApp.js  ****

import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter, Match } from 'react-router'
import Landing from './Landing'
import Search from './Search'
import Details from './Details'
// 1. ++++
import preload from '../public/data.json'
...

Stateless functional components

Details.js has no state, its stateless. 
So we could just do instead in details.js:
  const Details = () => {
  return <h1>hahahahah</h1>
}

export default Details
This still works. It's a stateless functional component. They're great and can be used everywhere. 
We can even pass down properties. 
  const Details = (props) => {
  return <h1>{props.params.id}</h1>
}

export default Details
This just a render method. You don't do createClass. You still need to import React though, because we're using JSX and ​<h1>​ is being transpiled to ​react.createElement...

16:10

We need to pass parameters to Search.js, but we just included it like so:
​<Match pattern='/search' component={Search} />

Instead, we need to return it from anonymous function :
  <Match 
  pattern='/search' 
  component={() => {
    return  <Search />
  }} 
/>
We need to pass properties to Search. 
  <Match 
  pattern='/search' 
  component={(props) => {
    return  <Search {...props} />
  }} 
/>
Then we need to pass the preload JSON data
  <Match 
  pattern='/search' 
  component={(props) => {
    return  <Search shows={preload} {...props} />
  }} 
/>

How props get from one place to another?

We need to preserve the properties that Browserrouter uses. With ​{...props}​ we pass all the props properties. 

Where does ​this.props​ come from?

In details.js: ​{JSON.stringify(this.props, null, 4)}
The router passes down the properties. It's React Router that instantiates our component and passes down the properties. 
Updates Search.js
  import React from 'react'
import ShowCard from './ShowCard'
// 3. ++--- use the props instead of preload
// import preload from '../public/data.json'
const { arrayOf, shape, string } = React.PropTypes

const Search = React.createClass({
  // 2. ++++ define the proptypes
  propTypes: {
    // array of elements
    // shape: objects of title and description
    shows: arrayOf(shape({
      title: string,
      description: string
    }))
  },
  getInitialState () {...},
  handleSearchTermChange (event) {...},
  render () {
    return (
      <div className='search'>
        <header>
          ...
        </header>
        <div>
        // 1. ++-- use the props
      // {preload.shows
          {this.props.shows
            .filter((show) => `${show.title} ${show.description}`.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0)
            .map((show) => {
              return (
                <ShowCard key={show.imdbID} {...show} />
              )
            })}
        </div>
      </div>
    )
  }
})

export default Search
Everything still works, but now the shows data is coming from ClientApp instead of being imported from the JSON. 

​arrayOf​ propType

Above, for the Proptypes definition, you could also just use ​array​, and not specify, the ​array​ of what. 
You can also create your own proptypes.

Make the ClientApp pass the correct show down to Details

  // ****   ClientApp.js    ****

import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter, Match } from 'react-router'
import Landing from './Landing'
import Search from './Search'
// 1. ++++
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>
        <div className='app'>
          <Match exactly pattern='/' component={Landing} />
          <Match
            pattern='/search'
            component={(props) => <Search shows={preload.shows} {...props} />}
          />
          // 2. ++++
          // pass down by the id
          <Match
            pattern='/details/:id'
            component={Details}
          />
        </div>
      </BrowserRouter>
    )
  }
})

render(<App />, document.getElementById('app'))
Branch v2-14

Proptypes declarations are only meant for documentation, and debugging (response to question).

Update clientapp details route
  // ****   ClientApp.js    ****
// ...
<Match
  pattern='/details/:id'
  component={(props) => {
  // filter the shows here
    const shows = preload.shows.filter((show) => props.params.id === show.imdbID)
    // then pass it to the component and 
    // return the  component 
    return <Details show={shows[0]} {...props} />
  }}
/>
// ...
The problem with the above code is that you assume that show id always exists.

Alternative to React Router?

He doesn't really know one. React Router is the de facto standard routing library for React.

Now, the Details receives the show details in ​this.props​. 
  // ****   Details.js    ****

<div className='details'>
  <pre><code>
  // this will show a property of "show"
  // with show details
    {JSON.stringify(this.props, null, 4)}
  </code></pre>
</div>
React Router v4 guide and docs:
http://react-router.now.sh

The v4 has a red icon, v3 is blue.

Component code ordering, top to bottom

  1. Declare the propTypes
  2. ​getInitialState​ functions
  3. life-cycle methods
  4. my own functions I wrote (eg. handlers)
  5. the ​render​ function
This is how Brian orders his code, but no obligation. 

Handling secure routes (authenticated routes)

He usually uses higher order components. 

Higher Order Components

A component that encapsulates behaviour but does not render anything on its own. For example the BrowserRouter component. Does not output markup.
For more details watch Henrik's workshop...

Proptypes are almost like TDD

You identify first what you need to have, the do it. So, he starts writing new components with the proptypes. 

Render the details with Details.js

So far it just dumped out the props...
  // ****   Details.js    ****

import React from 'react'
const { shape, string } = React.PropTypes

const Details = React.createClass({
  // 1. ++++ Define the proptypes
  propTypes: {
    show: shape({
      title: string,
      year: string,
      poster: string,
      trailer: string,
      description: string
    })
  },
  render () {
    // 2. ++++ grab the data you need
    const { title, description, year, poster, trailer } = this.props.show
    return (
      <div className='details'>
      // 3. ++++ add the markup
        <header>
          <h1>svideo</h1>
        </header>
        <section>
          <h1>{title}</h1>
          <h2>({year})</h2>
          <img src={`/public/img/posters/${poster}`} />
          <p>{description}</p>
        </section>
        <div>
        // 4. +++ also add the youtube video
        // nocookie! :)
        // frameBorder camelCase for React
          <iframe src={`https://www.youtube-nocookie.com/embed/${trailer}?rel=0&controls=0&showinfo=0`} frameBorder='0' allowFullScreen />
        </div>
      </div>
    )
  }
})

export default Details

How to conditionally pass data to child components?

With anonymous functions in React router. 
  // ****   ClientApp.js    ****
// ...
<Match
  pattern='/details/:id'
  component={(props) => {
    const shows = preload.shows.filter((show) => props.params.id === show.imdbID)
    return <Details show={shows[0]} {...props} />
  }}
/>
The component​ can contain whatever logic (code, scripts) you need, such as conditionals (like above, with ​filter​).

51:00

Move the header to a separate component since it's used in several places

Create Header.js

branch v2-15
  // ****   Header.js   ****

import React from 'react'
import { Link } from 'react-router'

class Header extends React.Component {
  render () {
    return (
      <header>
        <h1>
          <Link to='/'>
            svideo
          </Link>
        </h1>
      </header>
    )
  }
}

export default Header

The third way to create components with the ​class​ syntax

​class Header extends React.Component

Brian is not a big fan of the ​class​ in ES6. (Kyle neither...). But React wants to move away from their own way of creating classes, and use the JavaScript standard way. You do lose some conveniences with this syntax though.
This is how it's shown in the React docs. 

Add this to Details.js
  // ****   Details.js    ****

import React from 'react'
// 1. ++++
import Header from './Header'
const { shape, string } = React.PropTypes

const Details = React.createClass({
  propTypes: {
    show: shape({...})
  },
  render () {
    const { title, description, year, poster, trailer } = this.props.show
    return (
      <div className='details'>
      // 2. ++++
        <Header />
        <section>
        ...

Add a slightly advanced version of the header to search

We need to add a conditional logic to header. Not that straightforward with ES6 classes, since you cannot add properties to it. 
You have to define the ​Header.proptypes​ outside the ​class​ declaration.
  // ****   Header.js   ****

class Header extends React.Component {
  ...
}

// 1. ++++
const { func, bool, string } = React.PropTypes
Header.propTypes = {
  handleSearchTermChange: func,
  showSearch: bool,
  searchTerm: string
}
If you want to add your own functions to your component with the ES6 classes, you need to use the ​constructor​ method
  class Header extends React.Component {
  constructor (props) {
    super(props)
    
    this.state = {
      blah...
    }
  }
  
  render () {
    return (
      ...  
    )
  }
}
Another annoyance is that ES6 classes don't auto-bind the context (​this​) for you. So this will not work:
  class Header extends React.Component {
  constructor (props) {
    super(props)
    
    this.state = {
      blah...
    }
  }
  // 1. ++++ ad a method
  someMethod () {
    // 3. `this` context will not be correctly set!
    this.setState({blah:'string'})
  }
  
  render () {
    return (
      // 2. ++++ call the method
      <input onChange={this.someMethod()}>what</input> 
    )
  }
}
So then, you need to do explicit binding:
​<input onChange={this.someMethod.bind(this)}>what</input> ​ 
But this is not performant. The component will be rendered thousands times in your app, and each time ​bind​ will run. 
So then, to get around this, you need hijack the methods:
  class Header extends React.Component {
  constructor (props) {
    super(props)
    
    this.state = {
      
    }
    // 1. ++++ define the bound methods
    this.someMethod = this.someMethod.bind(this)
  }
  someMethod () {
    this.setState({blah:'string'})
  }
  
  render () {
    return (
      <input onChange={this.someMethod()}>what</input> 
    )
  }
}
This is very convoluted. This is why Brian prefers ​.createClass​. You don't have to worry about context. 

1:05:00

Branch v2-17

Finish the Header.js conditional

  // ****  Header.js  ****
import ...

class Header extends React.Component {
  render () {
    // 1. ++++
    let utilSpace
    // on search page:
    if (this.props.showSearch) {
      // move this from Search.js ↓
      utilSpace = <input onChange={this.props.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='Search' />
    } else {
      // on details page, go back to search:
      utilSpace = (
        <h2>
          <Link to='/search'>
            Back
          </Link>
        </h2>
      )
    }
    return (
      <header>
        <h1>
          <Link to='/'>
            svideo
          </Link>
        </h1>
        // 2. ++++ then render the 
        // utilSpace component
        {utilSpace}
      </header>
    )
  }
}

const { func, bool, string } = React.PropTypes
Header.propTypes = {
  ...
  
}

export default Header

Then use it on the search page

  // ****   Search.js   ****

import React from 'react'
import ShowCard from './ShowCard'
// 1. ++++
import Header from './Header'

..

 render () {
    return (
      <div className='search'>
      // 2. +++++
        <Header
        // set showSearch to true, just by adding it
        // no need to set it to true
          showSearch
          searchTerm={this.state.searchTerm}
          // pass down the handler 
          // for the search term change
          handleSearchTermChange={this.handleSearchTermChange}
        />
        <div>
          {this.props.shows
            .filter((show) => `${show.title} ${show.description}`.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0)
            .map((show) => {
              return (
                <ShowCard key={show.imdbID} {...show} />
              )
            })}
        </div>
      </div>
    )

If you add a JSX attribute, it's default value will be true, just like with normal HTML attributes. 

Now, we've successfully extracted Header (search input) and Search into different components and they still talk to each other, by passing ​handleSearchTermChange​ as a props to the sub-component (Header). 

Add link to the details page

  // ****   Showcard.js ****

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

const ShowCard = React.createClass({
  propTypes: {
    ...
    // 4. ++++
    imdbID: string.isRequired
  },
  render () {
    // 2. ++-- imdbID
    const { poster, title, year, description, imdbID } = this.props
    return (
      // 3. ++++ Add link
      <Link to={`/details/${imdbID}`}>
        <div className='show-card'>
          ...
        </div>
      </Link>
    )
  }
})

export default ShowCard
1:16:43

He adds ​.isRequired​ to all of his proptypes. As you prefer...

branch v2-1

Passing around functions between components

Explains passing the handleSearchTermChange​. We pass down the function, but since it's a method of the parent component, ​this​ is preserved (​this.setState({..})​).

Our app is almost finished. One more thing, and then refactor and Redux. 

React will very probably keep ​createClass

People like it, use it (like Netflix) so they won't deprecate it. If ever they will, they will surely create a codemod that will convert your code to 90%.

1:26:00

Life cycle methods

Core concept in React.

getInitialState

So far we've been using ​getInitialState​ life cycle method. (or ​constructor​ when using ES6 classes)

​componentWillMount

Called just before the component will be inserted into the DOM. He almost never uses it. 

​componentDidMount

Just after the component has been put into the DOM. More useful for him, because of universal, server-side rendering. componentWillMount is called in the NodeJS environment, while componentDidMount is not called in NodeJS env. 
Of course, in React you rarely want to interact with the DOM directly, but...
  • You want to do AJAX at this stage. 
  • interact with jQuery (if you have to) 
  • add side listeners (if you have to)
  • D3
If you navigate out from the "page" and then back, this will run again.

componentWillUnmount

Component is leaving the DOM. For cleanup. Remove event listeners. Unsubscribe from events. 

​getDefaultProps

Less used. Usually called at the root component. Usually used for dependency injection (Brian not a fan of dependency injection). Brian in fact never used this. 

​shouldComponentUpdate

Used for performance reasons. Calculating what has to re-render on deeply nested objects is very expensive. For example, here you can check if some change in the data should really trigger a component update or not. 
Or, if you want your component to never update, then you can call ​return​ on it (or ​return false​ not clear on the video). 1:35:20
Try to avoid messing with it, unless absolutely necessary. A. React is already very performance oriented, B. It can create maintainability issues later (you don't know why things don't update even if the data has changed).

React perf tools

Visualise where your application spends the most time. 
  • print inclusive → includes life-cycle methods
  • print exclusive → does not include life-cycle methods
  • print waisted → where repaints are waisted
This can help you find places to put ​shouldComponentUpdate​. 

Performance tips

  • avoid caching, memorisation (complicates code)
  • avoid inline SVGs. React can do it, but they're slow.
1:39:42 
Question about animation. Not clear the answer...

Most life-cycle methods are only useful for interacting D3...

AJAX request with Axios

Branch v2-17
  // ****   Details.js    ****

import React from 'react'
// 1. ++++ axios AJAX library
import axios from 'axios'
import Header from './Header'
const { shape, string } = React.PropTypes

const Details = React.createClass({
  propTypes: {
    show: shape({
...
    // 4. ++++
      imdbID: string
    })
  },
  // 2. ++++ 
  // for now just an empty object
  getInitialState () {
    return {
      omdbData: {}
    }
  },
  // 3. ++++
  componentDidMount () {
    // do the ajax
    // omdb is an open-source api for imdb
    axios.get(`http://www.omdbapi.com/?i=${this.props.show.imdbID}`)
      .then((response) => {
        this.setState({omdbData: response.data})
      })
      // handle the error
      .catch((error) => console.error('axios error', error))
  },
  render () {
    const { title, description, year, poster, trailer } = this.props.show
    // 4. ++++
    // uses rating to detect if the
    // data came back
    let rating
    if (this.state.omdbData.imdbRating) {
      rating = <h3>{this.state.omdbData.imdbRating}</h3>
    } else {
      // put up the spinner
      rating = <img src='/public/img/loading.png' alt='loading indicator' />
    }
    return (
      <div className='details'>
        <Header />
        <section>
          <h1>{title}</h1>
          <h2>({year})</h2>
          {rating}
          <img src={`/public/img/posters/${poster}`} />
          <p>{description}</p>
        </section>
        <div>
          <iframe src={`https://www.youtube-nocookie.com/embed/${trailer}?rel=0&controls=0&showinfo=0`} frameBorder='0' allowFullScreen />
        </div>
      </div>
    )
  }
})

export default Details
1:47:00

componentDidMount will request the data when navigating out and back, that we will fix with Redux. 

It works! And pretty fast! Has to throttle the network speed in Firefor responsive mode, to see the preloader before the response comes back (and fires ​this.setState({omdbData: response.data})​ that will fire the rerender.

Arrow functions and ​this​ binding 

Snippet:
  axios.get(`http://www.omdbapi.com/?i=${this.props.show.imdbID}`)
  .then((response) => {
    this.setState({omdbData: response.data})
  })
If above, you would be using regular ​function​ declaration, instead of ​=>​, then the ​this​ would be bound to the wrong context (probably the window). 
Arrow functions don't create new context, but use the same context as it was called in, in our case component (we're inside ​componentDidMount​ method).

The app is finished now. We will continue with devtools, Redux.