Title Text

render2018

Title Text

Hello! I'm Jack

@Jack_Franklin

www.javascriptplayground.com

Please ask questions

This day is for you to learn React - consider me interruptible at any point - please ask at all times!

Slides + Talk

I'll show you some slides, maybe some code, and talk you through some concepts.

 

I will share the slides at the end of the workshop :)

Exercise

We'll then stop and I'll ask you to do some exercises. You're going to be writing a lot of code today!

 

You won't pick everything up right away, no one does! Please ask lots of questions :)

Set up

  • Node version 8 (node --version)
  • npm version 5 or Yarn version 1 (npm --version, yarn --version)
  • https://github.com/jackfranklin/react-fundamentals-workshop cloned locally

    • {yarn, npm} install
  • An editor of your choice with the React plugin installed.
    
  • Join: https://tlk.io/jack-react

The React fundamentals.

We're going to talk about only React today.

Not a single third party library*

 

*ok there's one but I wrote it so that's allowed

By learning the core React framework and ideas, you'll be equipped to pick up, learn and work with any additional React libraries.

Components

React + the DOM

  • Tell React what each component should render.
  • React takes care of the DOM for you.

Running the workshop

npm run exercise 1

yarn run exercise 2

Let's get started!

yarn run exercise 1


npm run exercise 1
import '../common.css'

import ReactDOM from 'react-dom'
import React from 'react'

const HelloWorld = () => {
  // TODO: can you change the h1 to another element?
  // how would we give the h1 a class name?
  return React.createElement('h1', null, 'Hello World')
}

ReactDOM.render(
  React.createElement(HelloWorld),
  document.getElementById('react-root')
)

don't worry about this bit

import '../common.css'

import ReactDOM from 'react-dom'
import React from 'react'

const HelloWorld = () => {
  // TODO: can you change the h1 to another element?
  // how would we give the h1 a class name?
  return React.createElement('h1', null, 'Hello World')
}

ReactDOM.render(
  React.createElement(HelloWorld),
  document.getElementById('react-root')
)

import React and ReactDOM

import '../common.css'

import ReactDOM from 'react-dom'
import React from 'react'

const HelloWorld = () => {
  // TODO: can you change the h1 to another element?
  // how would we give the h1 a class name?
  return React.createElement('h1', null, 'Hello World')
}

ReactDOM.render(
  React.createElement(HelloWorld),
  document.getElementById('react-root')
)

our first component!

import '../common.css'

import ReactDOM from 'react-dom'
import React from 'react'

const HelloWorld = () => {
  // TODO: can you change the h1 to another element?
  // how would we give the h1 a class name?
  return React.createElement('h1', null, 'Hello World')
}

ReactDOM.render(
  React.createElement(HelloWorld),
  document.getElementById('react-root')
)

our first component!

Exercise 1

render our first component into the DOM

const HelloWorld = () => {
  // TODO: can you change the h1 to another element?
  // how would we give the h1 a class name?
  return <h1>Hello World</h1>
}

ReactDOM.render(<HelloWorld />,
 document.getElementById('react-root')
)

JSX!

remember, JSX gets compiled to React.createElement.

 

It's just JavaScript!

Exercise 2

const HelloWorld = props => {
  // TODO: pass through another prop to customise the greeting
  // rather than it always be hardcoded as Hello
  return <h1>Hello, {props.name}</h1>
}

ReactDOM.render(
  <HelloWorld name="Jack" />,
  document.getElementById('react-root')
)

we can pass properties (or, props) through to components to configure them or change their behaviour

Exercise 3

remember, props are all just JavaScript, so you can pass through any data type - these aren't HTML attributes

const bunchOfProps = {
  name: 'Jack',
  age: 25,
  colour: 'blue',
}

ReactDOM.render(
  <HelloWorld
    name={bunchOfProps.name}
    age={bunchOfProps.age}
    colour={bunchOfProps.colour}
  />,
  document.getElementById('react-root')
)

Exercise 4

ReactDOM.render(
  <HelloWorld {...bunchOfProps} />,
  document.getElementById('react-root')
)

or:

HelloWorld.propTypes = {
  name: PropTypes.string.isRequired,
  colour: PropTypes.oneOf(['blue', 'red']).isRequired,
}

Exercise 5

Documenting your components with prop types

 

This seems like a chore, but trust me, you'll thank yourself in the future!

 

https://reactjs.org/docs/typechecking-with-proptypes.html

const AskQuestion = () => {
  return <p>How is your day going today?</p>
}

const HelloWorld = props => {
  return (
    <div>
      <AskQuestion />
      <h1>
        {props.greeting}, {props.name}
      </h1>
    </div>
  )
}

Exercise 6

Components can render other components.

 

React components must start with a capital letter.

Managing state

Props

Data a component is given and uses but cannot change.

State

Data a component owns and can change.

Functional components

What we've used so far. These components cannot have state.

Class components

We define our components as classes. These components can have state.

import React, { Component } from 'react'

class MyComponent extends Component {
  constructor(props) {
    super(props)

    this.state = {...}
  }

  render() {
    return <p>Hello world</p>
  }
}

this is boilerplate you don't need to worry about

this is like the body of the functional components we have

been using so far!

Listening to user events

onButtonClickIncrement() {

}

render() {
  return (
    <div>
      <p>current count: {this.state.count}</p>
      <button onClick={this.onButtonClickIncrement.bind(this)}>
        Click to increment
      </button>
    </div>
  )
}

we have to bind to ensure the right scope within the event handler

 

(we'll see a nicer way to do this later on)

Updating state

this.setState({
  newValue: 3,
})

when the new state doesn't depend on the old state

Updating state

this.setState(function(previousState) {
  return {
    newValue: previousState.newValue + 1
  }
})

when the new state does depend on the old state

class Counter extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0,
    }
  }

  onButtonClickIncrement() {
    this.setState(prevState => {
      return { count: prevState.count + 1 }
    })
  }

  render() {
    return (
      <div>
        <p>current count: {this.state.count}</p>
        <button onClick={this.onButtonClickIncrement.bind(this)}>
          Click to increment
        </button>
      </div>
    )
  }
}

Exercise 7

Passing state to child components

const Parent = () => {
  return <Child />
}
      <div>
        <Count count={this.state.count} />
        <button onClick={this.onButtonClickIncrement.bind(this)}>
          Click to increment
        </button>
      </div>

Exercise 8

The Count component gets a count property, that has come from state of its parent.

Parent and child communication

Sometimes we'll have state in the parent

 

that we give to the child

<SomeChildComp foo={this.state.foo} />

And sometimes the child needs to let us know that the state has changed.

<SomeChildComp
  foo={this.state.foo} 
  onFooChange={this.onFooChange}
/>

Parent

Child

foo=this.state.foo

hey parent, foo changed!

Exercise 9

<Count 
  count={this.state.count1}

  onIncrement={this.incrementCount1.bind(this)}
/>

here's some data for you to render

and when that data changes, this is how you tell me about it

this.state = {
  counts: [0, 0, 0],
}

often we have lists of data that we want to render

    return (
      <div>
        <Count
          count={this.state.counts[0]}
          onIncrement={this.incrementCount.bind(this, 0)}
        />

        <Count
          count={this.state.counts[1]}
          onIncrement={this.incrementCount.bind(this, 1)}
        />
      </div>
    )

and this is a pretty manual way of doing it

Exercise 10

const numbers = [1, 2, 3]

const newNumbers = numbers.map(function(x) {
  return x * 2
})

// or

const newNumbers = numbers.map(x => x * 2)

newNumbers === [2, 4, 6]

mapping over arrays in JavaScript

Exercise 10

so could we take our array of counts, and map them into an array of <Count /> components?

this.state = {
  counts: [0, 0, 0],
}


this.state.counts.map((count, index) => {
  return (
    <Count
      count={count}    
      onIncrement={this.incrementCount.bind(this, index)
    />
  )
})

Exercise 11

React Children

<Count count={this.state.count} />
<Count count={this.state.count}>
  <p>some random child</p>
</Count>
this.props.children
<Count
  count={this.state.count}
  children={<p>some random child</p>}
/>

remember, props can be anything!

we'll see more usages of React's children later on :)

 

Time for a quick break! Any questions?

Quick aside

Tooling

This workshop purposefully avoids talking about tooling and set up. We're focusing purely on React today (but questions are welcome!)

But I want to take a few minutes to quickly talk about some of the things going on in the workshop behind the scenes.

Bundler

A tool that takes all of our code and generates a bundle of HTML, CSS and JS for us.

Today I'm using https://parceljs.org/, but I'm also a big fan of https://webpack.js.org/ 

Transpiler

A tool that takes our modern JS and converts it back into JS that older browsers can use. Depending on what browsers you support, these will do different transforms.

Today we're using https://babeljs.io/ with some plugins:

 

- babel-preset-env

- babel-preset-react

Object rest spread

Enables https://github.com/tc39/proposal-object-rest-spread, which is at Stage 4, meaning it will become part of JavaScript.

Class properties

Enables https://github.com/tc39/proposal-class-fields, which is a Stage 3 proposal.

Class properties mean much nicer event handlers.

onButtonClickIncrement() {

}

<button onClick={this.onButtonClickIncrement.bind(this)}>
onButtonClickIncrement = () => {

}

<button onClick={this.onButtonClickIncrement}>

becomes...

arrow functions are always bound to the scope of the thing that defined them

Foo.propTypes = {...}

constructor(props) {
  super(props)
  this.state = {...}
}
class Foo extends Component {
  static propTypes = {...}

  state = {...}
}

becomes...

Code formatting

I find formatting code rather mundane and boring - so I let tools do it for me!

https://prettier.io/ is fantastic and there are editor plugins available for all popular editors.

Dealing with async data

so-fetch-js

import fetch from 'so-fetch-js'

fetch('/users').then(response => {
  console.log(response.data)
})

https://jsonplaceholder.typicode.com/posts

[
  {
    userId: 1,
    id: 1,
    title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    body: "quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam 
            nostrum rerum est autem sunt rem eveniet architecto"
  },
  ...
]

https://jsonplaceholder.typicode.com/posts/1

Component lifecycle

https://reactjs.org/docs/react-component.html#the-component-lifecycle

componentDidMount

https://reactjs.org/docs/react-component.html#componentdidmount

If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

Exercise 12

  componentDidMount() {
    const urlForPost = `https://jsonplaceholder.typicode.com/posts/${
      this.props.id
    }`

    fetch(urlForPost).then(response => {
      const post = response.data
      // TODO: put this post into the state
      console.log('I got the post!', post)
    })
  }
{this.state.post === null && <div>Loading...</div>}

Conditional rendering in JSX

  render() {
    return this.state.post ? (
      <div>
        <h1>{this.state.post.title}</h1>
        <p>{this.state.post.body}</p>
      </div>
    ) : (
      <p>Loading</p>
    )
  }

forms in React

Controlled inputs

React controls the value of an input

 

And React controls the onChange event of an input.
            <input
              type="text"
              name="post-id"
              value={this.state.userPostInput}
              onChange={this.userInputChange}
            />









        <form onSubmit={this.onSubmit}>

value: the actual value of the input box

onChange: what React calls when the user types in the box, so we can update the value

userInputChange = event => {
  this.setState({
    userPostInput: event.target.value,
  })
}

event.target.value === the latest value from the input that the user has typed

Exercise 13 (unlucky for some)

        <form onSubmit={this.onSubmit} className="search-form">
          <label>
            Please enter the ID of a post
            <input
              type="text"
              name="post-id"
              value={this.state.userPostInput}
              onChange={this.userInputChange}
            />
          </label>
          <button type="submit">Go</button>
          {/* TODO: add another button that clears out the user input value */}
        </form>

        {this.state.searchId && (
          <p>The ID you searched for is: {this.state.searchId}</p>
        )}
  userInputChange = e => {
    console.log('got user input value', e.target.value)
    // TODO: update the userPostInput state with the new value when the user types
  }

  onSubmit = e => {
    e.preventDefault()
    console.log('got form submit!')
    // TODO: update the searchID state with the latest user post ID when the form is submitted
  }

Forms and APIs

Exercise 14

  onSubmit = e => {
    e.preventDefault()
    console.log('got form submit!')
    // TODO: call this.fetchPost(), passing in the right ID
  }

fetching based off user input

multiple components in multiple files

ES2015 Modules

export default class Post extends Component {
   ...
}
import Post from './post'

React lifecycle hooks

componentDidUpdate

https://reactjs.org/docs/react-component.html#componentdidupdate

Exercise 15

componentDidUpdate

componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.

 

Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).

Exercise 15

  componentDidUpdate(prevProps, prevState) {
  }

don't forget to check that the props or state have changed!

Exercise 15


  componentDidUpdate(prevProps, prevState) {
    if (prevProps.id !== this.props.id) {
    }
  }

if this check passes, we can do our work

Exercise 15

Exercise 15

PostSearch: the main component that lets a user search by post ID

 

Post: the component that takes a post ID, fetches it, then renders it.

        <form onSubmit={this.onSubmit} className="search-form">
          <label>
            Please enter the ID of a post
            <input
              type="text"
              name="post-id"
              value={this.state.userPostInput}
              onChange={this.userInputChange}
            />
          </label>
          <button type="submit">Go</button>
        </form>
        <Post id={this.state.searchId} />

Exercise 15

But there's a bug!

 

Filling in a new ID and hitting 'go' does not load the new post.

export default class Post extends Component {
  static propTypes = {
    id: PropTypes.number,
  }

  state = {
    post: null,
  }

  componentDidMount() {
    this.fetchPost()
  }
 
   ...
}

Extracting the user input.

Exercise 16

we've pulled out a component for getting the user's search ID, but it's not quite done yet.

class PostSearch extends Component {
  state = {
    searchId: 1,
  }

  onSubmit = id => {
    this.setState({ searchId: id })
  }

  render() {
    return (
      <div>
        <UserInput onSearchInputChange={this.onSubmit} />
        <Post id={this.state.searchId} />
      </div>
    )
  }
}

this is more parent to child

communication like we saw earlier

Let's take a break!

 

Any questions? :)

More "advanced" React

Let's talk about fetching posts

<div>
  <UserInput onSearchInputChange={this.onSubmit} />
  <Post id={this.state.searchId} />
</div>

this is our render function, but we have no control over how it's rendering!

<Post /> contains logic about fetching a post that we don't want to care about.

 

But we want to have control over what to render.

Over the next few exercises, we'll see different techniques for solving this problem

each one improves on the previous...so stick with it!

1. Compound components with mapping children.

https://reactjs.org/docs/react-api.html#reactchildrenmap

React provides an API to take all the children given to a component and edit them.

// imagine this.props.children are all <p>foo</p>

return React.Children.map(this.props.children, child => {
  // we can edit the child and return a new one
})

https://reactjs.org/docs/react-api.html#reactchildrenmap

React also gives us cloneElement to take copies of the child but give it extra properties

// imagine this.props.children are all <p>foo</p>

return React.Children.map(this.props.children, child => {
   return React.cloneElement(child, { className: 'foo'})
})

so in our case, the Post component can...

 

  1. Take a child component that can output the post
     
  2. Use React.children.map to pass the `post` property into its child.
const PostOutput = props =>
  props.post ? (
    <div>
      <span>Loaded post ID: {props.post.id}</span>
      <h1>{props.post.title}</h1>
      <p>{props.post.body}</p>
    </div>
  ) : (
    <p>Loading</p>
  )
        <Post id={this.state.searchId}>
          <PostOutput />
        </Post>



    return React.Children.map(...)

Exercise 17

Problems with this approach?

  • It's very implicit - from looking at the render method we have no idea that this is happening.
  • It ties <PostOutput> to being used within <Post>
  • We can't just put any old code within <Post>, it has to be a specific component that is expecting a specific prop.

2. Higher order components

Higher order?

const createAdder = x => {
  return function(y) { return x + y };
}

const createAdder = x => y => x + y

const addTwo = createAdder(2)

addTwo(2) // 4

Higher order component?

const wrapWithDiv = Component => {
  const NewComponent = props => (
    return <div><Component {...props}</div>
  )

  return NewComponent
}

const Hello = () => <p>Hello world!</p>
const WrappedComponent = wrapWithDiv(Hello)

<WrappedComponent />

functions that return React components

Exercise 18

const withPost = ChildComponent => {

  return class Post extends Component {
    static propTypes = {
      id: PropTypes.number,
    }

    state = {
      post: null,
    }

    ...

  

    render() {
      return <ChildComponent {...this.props} post={this.state.post} />
    }
  }

}

export default withPost

takes a ChildComponent as an argument

we return a component that renders our child component with some extra props

import withPost from './post'

How does this approach compare?

  • It's more explicit, and the withPost function at least makes it clear that something is going on.
  • Still creates some indirection and harder to follow code.
  • The component we pass into withPost still has to know that it will end up being given a `post` prop.

Render functions

🔥

What if we gave <Post /> a function to call with the post it has fetched?

So it deals with the data fetching logic, but passes rendering control back to us.

export default class Post extends Component {
  static propTypes = {
    id: PropTypes.number,
    render: PropTypes.func.isRequired,
  }

  ...

  render() {
    return this.props.render(this.state.post)
  }
}

takes a render prop that we call to render

        <Post
          id={this.state.searchId}

          render={post =>
            post ? (
              <div>
                <span>Loaded post ID: {post.id}</span>
                <h1>{post.title}</h1>
                <p>{post.body}</p>
              </div>
            ) : (
              <p>Loading</p>
            )
          }

        />

Exercise 19

        {/* TODO: update this render function to output the post if we have one
             or render "Loading" if we don't have a post
            */}
        <Post id={this.state.searchId} render={() => null} />
        {/* TODO: once you've done that, pull that logic into a PostOutput component
          * (hint: you'll find the prop types above) and use that within the render func
          */}

Render functions 2: an alternative approach

passing a function as the child prop

<Post id={this.state.searchId} render={() => null} />






<Post id={this.state.searchId} children={() => null} />

these are equivalent but one uses the prop `render` and the other `children`

<Post id={this.state.searchId}>{() => null}</Post>

but we can also pass the children prop in implicitly.

 

This is the same as using the `children=` prop syntax!

(note that exercise 20 is broken at the start on purpose! Look at 20.js to see what to do)

Exercise 20

        <Post id={this.state.searchId}>
          {post => <PostOutput post={post} />}
        </Post>




 
        <Post
          id={this.state.searchId}
          render={post => <PostOutput post={post} />}
        />

can we make Post support both of these?

So how does this compare?

  • Very explicit! It's obvious what's going on
  • There is no "magic" or implicit things happpening
  • We have full control over how to render and what we render.

Let's take another break! 

Any questions? :)

React's context API

Important! This is NOT released yet.

It's due out in React 16.3.0, which is due reasonably soon.

If you look up "context in React" today, it's different and about to be removed.

Which is why, today, we're teaching the "bleeding edge" new context API. 

Small details could change but the approach will not.

We're going to use a library for this that mimics what will be in React.

https://github.com/jamiebuilds/create-react-context

What is context?

A way to share data in a big tree of components

this.state.foo = 1
this.state.foo = 1
hey, I need to know `foo` !
this.state.foo = 1
foo={this.state.foo}
foo={this.props.foo}
foo={this.props.foo}
foo={this.props.foo}
this.state.foo = 1
foo={this.state.foo}
foo={this.props.foo}
foo={this.props.foo}
foo={this.props.foo}
this.state.foo = 1
foo={...}

the 3 middle components

don't know or care about foo.

const Context = createReactContext(defaultValue);
// <Context.Provider value={providedValue}>{children}</Context.Provider>
// ...
// <Context.Consumer>{value => children}</Context.Consumer>
<ThemeContext.Provider value={this.state.theme}>
  {this.props.children}
</ThemeContext.Provider>

// and then later on in any child component

<ThemeContext.Consumer>
  {theme => (
    <h1 style={{ color: theme === 'light' ? '#000' : '#fff' }}>
      {this.props.children}
    </h1>
  )}
</ThemeContext.Consumer>

Provider

The component  that makes a value accessible to all consumers in the component tree.

Consumer

The component that can read the value of a piece of state.

Creating a context gives you:

<MyBlog /> this.state.signedIn
<Posts />
<Post post={post} />
<UserActions />
<MyBlog /> this.state.signedIn
<Posts />
<Post post={post} />
<UserActions />

user actions needs to know if the user is signed in, to know if they can perform the actions

import createReactContext from 'create-react-context'

const AuthContext = createReactContext(false)

export default AuthContext

default value

import AuthContext from './auth-context'     

   <div>
          <h1>Blog posts by Jack</h1>
          <AuthContext.Provider value={this.state.signedIn}>
            <Posts />
          </AuthContext.Provider>
        </div>

value for any consumers

any consumer in <Posts /> or below can now consume our auth context

import AuthContext from './auth-context'     

<AuthContext.Consumer>
  {signedIn => (
    <div>signed in? {signedIn === true ? 'yes' : 'no'}</div>
  )}
</AuthContext.Consumer>

Exercise 21!

class MyBlog extends Component {
  state = {
    signedIn: false,
  }

  signIn = () => {
    this.setState({ signedIn: true })
  }

  signOut = () => {
    this.setState({ signedIn: false })
  }

  render() {
    return (
      <div>
        <header>
          {this.state.signedIn ? (
            <Fragment>
              <span>Signed in as jack</span>
              <button onClick={this.signOut}>Sign Out</button>
            </Fragment>
          ) : (
            <button onClick={this.signIn}>Sign In</button>
          )}
        </header>
        <div>
          <h1>Blog posts by Jack</h1>
          <AuthContext.Provider value={this.state.signedIn}>
            <Posts />
          </AuthContext.Provider>
        </div>
      </div>
    )
  }
}

using context in our blog post app

Exercise 22

our app currently only exposes if the user is signed in or not. Can we make it so the user can sign in with a username, and then we show that username throughout the site?

 

Add a small input to the header so a user can "sign in" with their username, and then add the user's username to the `AuthContext` so it can be used in our application.

Let's take a break.

Building your own app

create-react-app

https://github.com/facebookincubator/create-react-app

yarn global add create-react-app


npm install --global create-react-app
create-react-app my-app


cd my-app && yarn start

this is the best way to get started building a React app!

an app to list github repositories for a username

github-proxy-api.herokuapp.com

/users/jackfranklin/repos

Using create-react-app, build an app that will:

  • Let me enter a username.
  • Show me a list of all that user's repositories on GitHub.

yarn add so-fetch

github-proxy-api.herokuapp.com

/users/jackfranklin/repos

Testing React course

 

Use the code "RENDERCONF" for 50% off :)

 

https://javascriptplayground.com/testing-react-enzyme-jest/

React Fundamentals

By Jack Franklin

React Fundamentals

  • 1,023