Skip to content

Instantly share code, notes, and snippets.

@chadfurman
Last active March 27, 2017 04:36
Show Gist options
  • Save chadfurman/8e75689d875c32f0b3472e3799b7e2a3 to your computer and use it in GitHub Desktop.
Save chadfurman/8e75689d875c32f0b3472e3799b7e2a3 to your computer and use it in GitHub Desktop.
Frontend Masters React v2 3/21, 3/27

Babel Configuration

  • .babelrc (required json file)
  • Presets and Plugins -- a preset is a group of plugins
  • es2015 preset is all the plugins needed to get from es6 to es5
  • presets: ["es2015", {"modules": false}] <-- config for es2015 to make it so babel doesn't process modules
    • we do this for treeshaking
    • es2015 has a "loose": true config option

Creating a Webpack config.js File

  • webpack --module-bind="js=babel' js/ClientApp.js (all JS files get loaded through babel)
  • We could drop this into npm scripts, but let's make a config file because it's easier to maintain
  • webpack.config.js
// we're using default node include syntax to save effort of parsing es2015
const path = require('path') // node module that helps you resolve paths

module.exports = {
  context: __dirname,
  entry: './js/ClientApp.js',
  devtool: 'source-map', // don't use devtool on production build
  output: {
    path: path.join(__dirname, '/public')
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.js', '.json']
  },
  stats: { // what kind of stuff does webpack report on
    colors: true, // because coloes
    reasons: true, // why things fail
    chunks: true // ...
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader' // if it passes this test, run it through this loader (we installed this npm module)
      }
    ]
  }
}
  • Webpack is a module bundler
  • Gulp and Grunt are task runners
  • with our config file, we should be able to just run webpack and it should just work
  • There's an entire course on configuring webpack
  • `webpack --watch
  • npm run build -- --watch
  • npm watch
package.json:
"scripts": {
  "watch": "npm run build -- --watch"
}

JSX

  • Facebook launched React at a conference. People saw HTML inside of their JS and they didn't want it.
  • JSX is XML-Like syntax for JS that adds some HTML into your JavaScript
    • React, without JSX, is JavaScript code to mimic markup
import React from 'react'

var div = React.DOM.div
var h1 = React.DOM.h1

var MyTitle = React.createClass({
  render: function() {
    const style = {color: this.props.color}
    return (
      <div>
        <h1 style={ style }>
          {this.props.title}
        </h1>
      </div>
    )
  }
})
  
export default MyTitle
  • Having JS and HTML together like this adds a single entry-point for bug hunting
  • the curly braces mean to spit-out the result of the js expression

JSX vs createElement

  • createFactory is no longer useful, because of JSX
  • webcomponents are coming -- if it's your component, it must be uppercase, if it's a literal tag, use lowercase
  • Components can bemade up of multiple other components
  • Going too-small w/ components is both harder to reason about and performance-heavy

Configuring CSS Imports

  • React, React Router, Redux, Redux-thunk
  • we need to use both the style and css loader as module rules in webpack config, we could also use less or sass here
  • url: false? watch the webpack config -- don't inline images

Importing CSS in React

import React from 'react'
import { render } from 'react-dom' // I just want the render function -- webpack can eliminate dead code
import '../public/normalize.css'
import '../public/style.css'

const App = React.createClass({
  render () {
    return (
      <div className='app'> // className is the JavaScript API name
        <div className='landing'>
          <h1>svideo</h1>
          <input type="text" placeholder='Search' />
          <a>or Browse All</a>
        </div>
      </div>
    )
  }
})

render(<App />, document.getElementById('app'))

Linting rules for React

  • Standard is not configurable, so if you want to extend it, you must wrap it with eslint
  • .eslintrc.json
{
  "extends": ["standard", "standard-react"]  // standard-react is pretty much required
}
  • Now our linter must run via eslint, now. Edit package.json:
"lint": "eslint js/**/*.js js/**/*.jsx webpack.config.js" // lint everything in the js folder that ends in js or jsx, include webpack config, recurse
  • npm run lint -s will suppress npm output
  • npm run lint -s -- --fix

Automated Linting

  • add "enforce": "pre" rule to webpack config
  • test will be the same as the other js test
  • loader is eslint-loader
  • "exclude": /mode_modules
  • watch is smart enough to only lint things that change
  • enforce: pre is synonamous with preloaders

Webpack Development

  • webpack config:
{
  devServer: {
    publicPath: '/public'
  }
}
  • package.json:
"scripts": {
  "dev": "webpack-dev-server"
}
  • npm run dev has watch built-in

Routing, Props, & State Management

  • ReactRouter4 is in alpha
  • React Router 4 is a total re-write, and got so much better that it's worth it.
  • Multiple pages single pagey

HashRouter

  • HashRouter doesn't show you anything it just encapsulates behavior (aka Higher Order Components / Behavior oriented Components)
  • Usually your root component
  • Site renders inside
  • * w/o exactly, this will match _all_ urls because they all start with /
  • /#/route -- avoids dealing with back-end routes
  • If you can avoid HashRouter, use BrowserRouter
  • node can consume react router routes
  • comments: {/* ... */} -- html comments don't work

Creating the search page route

import React from 'react'

constSearch = React.createClass({
  render() {
    return (
      <h1>Search Page!!</h1>
    )
  }
})

export default Search
  • There is an unsafe render function, but don't use it
  • New route:
import React from 'react'
import { render } from 'react-dom'
import { HashRouter, Match } from 'react-router'
import Landing from './Landing'
import Search from './Search'
import '../public/normalize.css'
import '../public/style.css'

const App = React.createClass({
  render () {
    return ( 
      <HashRouter>
        <div className='app'>
          <Match exactly pattern='/' component={Landing} />
          <Match pattern='/search' component={Search} />
        </div>
      <HashRouter>
    )
  }
})
  • Nesting routes is possible/useful but no example
  • What about browser router?
  • give one more option to webpack config:
devServer: {
  publicPath: '/public/',
  historyApiFallback: true
}
  • ClientApp.js -- change HashRouter to BrowserRouter
  • `import { Link } from 'react-router' much better than "" tags
<Link to='/search'>or Browse All</Link>
  • Props: this.props.title, this.props.color, etc
  • Match, Miss, Redirect

Webpack Config

  • Another loader to tell webpack how to consume json
{
  test: /\.json$/,
  loader: 'json-loader'
}
  • also, import preload
import preload from '../public/data.json'
...
<div className='search'>
  <pre><code>{JSON.stringify(preload, null, 4)}</code></pre>
</div>
  • now we have a blob of data output to the screen

Iterating through Data

  • Taking array of data, mapping it to a new data structure -- functional programming 101
var numbers = [1,5,8]
// if I want to double, I could use a for-loop
// or I could use 'map' which takes a funcion and applies it to the elements in the array
numbers.map(function(number) {
  return number*2
})
// numbers = [2, 10, 16]
  • could also say:
var double = function(number) {
  return number*2
}
numbers.map(double)
  • so we get
{preload.shows.map(function (show) {
  return (
    ...
  )
})}
  • and this iterates through our data
  • most best-practices in React are best practices in JS
  • We can use arrow-functions:
{preload.shows.map((show) => {
  return (
    ...
  )
})}

Creating the ShowCard component

{preload.shows.map((show) => {
  return (
    <div className='show-card'>
      <img src={`/public/img/posters/${show.poster}`} /> // back-ticks for template-string
      <div>
      <h3>{show.title}</h3>
      <h4>({show.year})</h4>
      <p>{show.description}</p>
    </div>
  )
})}
  • Probably still getting an error about 'keys'? We'll talk about this in a moment.
  • When do we split out into new components?
    • If you're doing alot of markup inside of a map, you want to do it in a new component
import React from 'react'

const ShowCard = React.createClass({
  render () {
    const { poster, title, year, description } = this.props.show // destructuring to save typing
    return (
      <div className='show-card'>
        <img src={`/public/img/posters/${poster}`} /> // back-ticks for template-string
        <div>
        <h3>{title}</h3>
        <h4>({year})</h4>
        <p>{description}</p>
      </div>
    )
  }
})
  • import ShowCard into Search
import React from 'react'
import Showcard from './ShowCard'
import preload from '../public/data.json'

const Search = React.createClass({
  render() {
    return (
      <div className='search'>
        {preload.shows.map((show) => {
          return (
            <ShowCard show={show} />
          )
        })}
      </div>
    )
  }
})

export default Search

Key Props

  • React asking for our help to optimize our code
  • Imagine we have a sort feature in our UI -- we are triggering re-renders
  • The "key" property makes it possible to know which objects on the re-render have changed
 {preload.shows.map((show) => {
   return (
     <ShowCard key={show.imdbID} show={show} /> // key must simply be unique
   )
 })}

PropTypes

  • PropType is enforced by standard-react
const { shape, string } = React.PropTypes

const ShowCard = React.createClass({
  propTypes: {
    // things we expect from our parent
    show: shape({ // shape is a word for object
      poster: string,
      title: string,
      year: string,
      description: string
    })
  },
  ...
})
  • There's a bunch of different PropTypes
  • string.isRequired

Using the spread operator in JSX

  • {...show} can be used when passing an object into ShowCard
  • Breaks all the properties into separate properties
  • i.e. title=show.title poster=show.poster etc
  • No need to use shape as much -- remove it from import
 ...
 propTypes: {
   // things we expect from our parent
   poster: string,
   title: string,
   year: string,
   description: string
 },
 ...

Managing State

  • If you have a problem with state, the only place it could originate from is within the component itself
  • If you want to change the state, it must happen in the component itself
  • There's no way to push data "up" from a child component -- one-way data flow
  • The way you share data is by using a common ancestor
  • Child can use props to trigger events which parents listen to -- causing parents to modify their own state
  • Let's say we put a header on the page:
<header>
  <h1>svideo</h1>
  <input type='text' placeholder='search'>
</header>
  • Noone is listening to the input box...

getInitialState() and setState()

createClass({
  getInitialState () {
    return {
      searchTerm: 'an empty string or a random string if you just want to debug'
    }
  },
  ...
    <h1>{this.state.searchTerm}</h1>
    <input value={this.state.searchTerm} .. />
  ...
  • This does not enable 2-way data binding -- typing in the search box still won't change state
  • The event that fires when you let up the key is caught by React, but nothing is bound to it
  • Events don't escape REact
  • We have to give the input an onChange listener
...
handleSearchTermChange (event) {
  // event is a React synthetic-DOM event, but has the same API as the MDN event
  // use setState all the time
  this.setState({searchTerm: event.target.value}) // this is the "change" that we want to make to the state.
  // The alternative of setState:
  //    this.state.searchTerm = event.target.value
  //    this.forceUpdate() -- you shouldn't ever need this.
},
...
<input value+{...} onChange={this.handleSearchTermChange}
  • The virtualDom just makes React feasible, not necessarily more desireable
  • "Given these props and this state, my app looks like this..."
  • Given these sets of props, I have this search component
  • Question: How would you re-render after a debounced event?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment