Title Text

The React fundamentals.

We're going to talk about only React for now.

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.

Let's get started!

Today we're going to be building a journal app that will let you keep a daily diary.

We'll start small and build it out as we learn more about React and what it can do for us.

Here's the code for exercise 1. Let's walk through it together first.

const JournalApp = () => {
  // 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')
}

Let's render our first component and style the header correctly.

Time for exercise 1!

React.createElement is quite verbose...

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

<HelloWorld /> is equivalent to: React.createElement(HelloWorld)

 

<h1 className="foo">Hello</h1> is equivalent to React.createElement('h1', { className: 'foo' }, 'Hello')

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

<HelloWorld
  someProp={() => console.log('I am a function')}
  someOtherProp={['an array', 'of things']}
  someNumber={4}
  someString="4"
/>

remember, this is not HTML and you are not limited to passing around strings.

Exercise 3

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

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

this is quite a lot to type and means manual work if the object gets more properties that you want to be props

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

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

Exercise 4

the JSX spread syntax saves us manually listing all props and lets us apply an object as props to a component

it's easy to forget to pass a prop that a component needs, or make a typo

import ReactDOM from 'react-dom'
import React from 'react'
import PropTypes from 'prop-types'

const JournalApp = props => {
   ...
}

JournalApp.propTypes = {
  // TODO: define the prop type for the name, age and location prop
}
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

note that not all my examples use prop-types today, but that's just to keep them focused. My real code always uses them!

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.

defining and then using a component

Managing state

Props

Data a component is given and uses but cannot change.

State

Data a component owns and can change.

Who has heard of React hooks?

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.

In React 16.7 and before

Updating state

To update the state, we have to use functions that React provides.

 

This ensures React knows about our state change, and can act accordingly.

Updating state

this.setState({
  newValue: 3,
})

In React 16.7 and before

React 16.8

Hooks!

Hooks solve a wide variety of seemingly unconnected problems in React that we’ve encountered over five years of writing and maintaining tens of thousands of components.

We are going to exclusively use hooks for this workshop

Because they have been widely adopted and are considered superior to the class based approach.

And conceptually they are easier to learn and understand :)

Hook one: useState

import {useState} from 'react'

const [name, setName] = useState()
const [name, setName] = useState(0)





const nameState = useState(0)
const name = nameState[0]
const setName = nameState[1]

useState

these are equivalent

but destructuring is much nicer

const [name, setName] = useState(0)



useState

the value itself

a function we use to set the value

const JournalHeader = () => {
  // TODO: can you get rid of the `name` constant, and instead create some
  // state using useState ? and then when the login button is clicked, set the
  // name of the logged in person

  const name = 'Jack'

  const login = () => {
    console.log('I was clicked!')
  }

  return (
    <div className="journal-header-wrapper">
      <h1 className="journal-header">Journal App</h1>
      <h2 className="journal-subheader">Journal for {name}</h2>
      <button className="journal-login" onClick={login}>
        Login
      </button>
    </div>
  )
}

Exercise 7

Passing state to child components

Lifting state up

const Parent = () => {
  return <Child />
}
const JournalApp = () => {
  return <JournalHeader />
}

Parent

Child

logged in name

it makes more sense for the parent component (JournalApp) to know the logged in user's name

Parent

Child

logged in name

this technique is known as lifting state up, and it's an important one to be comfortable with.

Parent

Child 1

parent owns the name state

Child 2

so now both its children can access it

      <div>
        <JournalHeader name={name} />
      </div>

Exercise 8

If we have `name` as a piece of state, we can pass that as a prop to the journal header

but: how can we now have the login button update the state? Don't worry about this for now! That's the next exercise!

for now: const [name, setName] = useState('Jack') lets you define the starting value of a piece of state

This is a very common, and powerful pattern.

Take state that's in the parent, and pass it as a prop to a child (or many children).

Parent and child communication

Sometimes we'll have state in the parent

that we give to the child

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

JournalApp

JournalHeader

name

hey parent, name changed!

Exercise 9

<JournalHeader 
  name={name}
  onNameChange={setName}
/>

here's some data for you to render

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

Rendering lists of data

const posts = [
  { id: 1, title: 'A fun day at the beach', date: '2019-04-10', body: '...' },
  { id: 2, ... },
  ...
]

often we have lists of data that we want to render

and we want to automatically render new posts if the length changes

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 posts, and map them into an array of <li>s ?

<ul>{posts.map((post, index) => {
  return (
    <li>{post.title}</li>
  )
})}</ul>

Creating a <Post /> component to render a post

      <ul>
        {posts.map(post => {
          return <li key={post.id}>{post.title}</li>
        })}
      </ul>






      <ul>
        {posts.map(post => {
          return <Post key={post.id} post={post} />
        })}
      </ul>

Exercise 11

(We will start splitting up our components into multiple files soon!)

Creating a <Post /> component to render a post

you should get familiar with spotting when a bunch of HTML that's being rendered belongs in a separate component. Don't fear having lots of little components!

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 presets.

 

Talk to me about this later if you're interested :)

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.

Getting data from a source.

so-fetch-js

import fetch from 'so-fetch-js'

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

You all have a local API running on your machine :)

http://localhost:3000/posts

Component lifecycle

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

Before React hooks

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.

Before React hooks

useEffect

Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.

WITH React hooks 😻

side effects

<Post id={1} />








<li>Post: ...</li>

React takes our JSX and renders HTML onto the screen. It will do this every time any data updates.

useEffect(() => {...})

The process of rendering and re-rendering can trigger side effects, which we define via the useEffect hook.

  const [posts, setPosts] = useState(null)

  useEffect(() => {
    console.log('I get run on every render')
  })

useEffect

by default, useEffect runs on every single render

useEffect

✋🛑

 

There are gotchas here!

  const [posts, setPosts] = useState(null)

  useEffect(() => {
    console.log('I get run on every render')
  })

useEffect

this can be dangerous and lets you easily get into an infinite loop situation!

  const [posts, setPosts] = useState(null)

  useEffect(() => {
    console.log('I get run on every render')
  })

useEffect

so useEffect takes a second argument: a list of dependencies, or:

things that if they change, we should re-run our effect.

  const [posts, setPosts] = useState(null)

  useEffect(() => {
    console.log('I get run on every render')
  }, [])

useEffect

empty array = there is nothing that would cause this effect to have to run again.

 

so it will only run once!

Exercise 12

let's use useEffect to fetch some posts

modelling the loading state and showing a spinner

const [posts, setPosts] = useState(null)

default state of posts

default state of null, not of an empty array

Conditional rendering in JSX


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

you get used to the ternaries! They fit really nicely into JSX.

Update our journal to show a loading spinner whilst the posts are loading.

 

Hint: test the spinner by removing the `setPosts` call - else the posts load too quickly for you to see the spinner!

Exercise 13

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={userPostInput}
              onChange={e => setUserPostInput(e.target.value)}
            />








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

            <input
              type="text"
              name="post-id"
              value={userPostInput}
              onChange={e => setUserPostInput(e.target.value)}
            />








e.target.value : the value inside the input at the time of the event

let's let the user log in and tell us their name!

Exercise 14

            <input
              type="text"
              name="post-id"
              value={userPostInput}
              onChange={e => setUserPostInput(e.target.value)}
            />








hook up the form in the login modal to log the user in

Getting posts per user.

/posts?userId=1

 

fetches just posts for that user

 

PS: this is not a very secure API... 😱

so, when a user logs in, let's get their posts

  const userIdForName = (name) => {
    return {
      'alice': 1,
      'bob': 2,
      'charlotte': 3
    }[name]
  }

how do we make useEffect re-run when something changes?

the dependency array!

  useEffect(() => {
    const userId = userIdForName(name)
    if (!userId) return

    fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
      setPosts(response.data)
    })
  }, [name])

this is the important bit

Exercise 15

useEffect is incredibly powerful

multiple components in multiple files

ES2015 Modules

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

My convention

File is named with the same name as the component, in lowercase.

posts.js = Posts component

Your convention

You should come up with your own rules that suit you and your team!

Let's tidy up our code

Can you extract the JournalHeader component into its own file?

Exercise 16

Extracting components

The journal header now contains a modal which could exist on its own

This would be cleaner and nicer:

const JournalHeader = props => {
  const [isShowingModal, setIsShowingModal] = useState(false)

  const showModal = () => setIsShowingModal(true)

  return (
    <div className="journal-header-wrapper">
      <h1 className="journal-header">Journal App</h1>
      <h2 className="journal-subheader">Journal for {props.name}</h2>
      <button className="journal-login" onClick={showModal}>
        Login
      </button>

      <LoginModal
        isShowing={isShowingModal}
        onSubmit={name => props.setName(name)}
        onClose={() => setIsShowingModal(false)}
      />
      )}
    </div>
  )
}
const JournalHeader = props => {
  const [isShowingModal, setIsShowingModal] = useState(false)

  const showModal = () => setIsShowingModal(true)

  return (
    <div className="journal-header-wrapper">
      <h1 className="journal-header">Journal App</h1>
      <h2 className="journal-subheader">Journal for {props.name}</h2>
      <button className="journal-login" onClick={showModal}>
        Login
      </button>

      <LoginModal
        isShowing={isShowingModal}
        onSubmit={name => props.setName(name)}
        onClose={() => setIsShowingModal(false)}
      />
      )}
    </div>
  )
}

Exercise 17

can you extract LoginModal so this works?

Showing posts

We'd like to update the <Post /> component so you can click on a post and expand it to see its contents.

We'd like to update the <Post /> component so you can click on a post and expand it to see its contents.

 

Can you introduce some state and a UI to the <Post /> component to make this happen?

Exercise 18

Advanced React

If you stopped now, you'd be set to build lots of React apps.

Welcome to Advanced React!

All the exercises for the fundamentals + advanced live in the same repository.

Today the exercises are much more discussion based - many approaches today have pros and cons.

Hooks

 

How do people feel about hooks?

 

useState?

useEffect?

Custom hooks

Logging in every time is annoying, isn't it?

 

Let's store the logged in username in local storage*

 

What would be nice if is we had a custom hook that did this for us:

 

const [username, setUsername] = useLocalStorage('')

 

*PS: this is not a good secure solution but it'll do for our needs!

Exercise 18

React's context API

 

Or, since hooks: the useContext hook.

What is context?

A way to share data in a big tree of components

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

the 3 middle components

don't know or care about foo.

const Context = React.createContext(defaultValue);

React lets us define contexts that allow data to be shared without having to pass it through every layer on the component tree.

<ThemeContext.Provider value={'light'}>
  <MyApp />
</ThemeContext.Provider>

// and then later on in any child component

<ThemeContext.Consumer>
  {theme => (
    <h1 style={{ color: theme === 'light' ? '#000' : '#fff' }}>
      Hello world
    </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:

and you can read from a piece of context using the useContext hook! 🎉

<MyBlog /> signedIn
<Posts />
<Post post={post} />
<UserActions />
<MyBlog /> 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 React from 'react'

const AuthContext = React.createContext(false)

export default AuthContext

default value

import AuthContext from './auth-context'     

   <div>
          <h1>Blog posts by Jack</h1>
          <AuthContext.Provider value={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' 
import React, { useContext } from 'react'    

const signedIn = useContext(AuthContext)

get the latest value of the AuthContext

let's rework our user login system using context

import { createContext } from 'react'

const AuthContext = createContext({
  loggedInUserName: null,
})

export default AuthContext

AuthContext for our Journal

const [name, setName] = useState('') 

 const authContextValue = {
    loggedInUserName: name,
  }

Create the context based off the name state.

      <AuthContext.Provider value={authContextValue}>
        <JournalHeader name={name} setName={setName} />
        ...
      </AuthContext.Provider>

Wrap our app in the provider.

import AuthContext from './auth-context'
import React, { useContext } from 'react'

const authContext = useContext(AuthContext)

const name = authContext.loggedInUserName

Update <JournalHeader /> to read the name from the context, and not be passed it as a prop

Exercise 19

Q: what are the pros and cons of context over passing props?

Updating the name via the context

Right now we read the name from context, but set it via a prop that's passed down. Let's fix that.

Update our AuthContext so it exposes the logged in name and a function that <JournalHeader /> can call to set it.

Exercise 20

Avoiding additional work when possible.

Only updating the context if values we care about change.

  console.log('Updating the context')
  const authContextValue = {
    loggedInUserName: name,
    setLoggedInUser: setName,
  }

Calculate context on every render.

  console.log('Updating the context')
  const authContextValue = {
    loggedInUserName: name,
    setLoggedInUser: setName,
  }

But we only want to update the context if the `name` changes. So we're wasting effort here.

useMemo to the rescue

 

memo = memoization

 

useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

  const authContextValue = useMemo(() => {
    console.log('Updating the context')
    return {
      loggedInUserName: name,
      setLoggedInUser: setName,
    }
  }, [name])

useMemo to the rescue

 

the values that, when changed, should cause the context to be recalculated

(just like with useEffect!)

  const authContextValue = useMemo(() => {

  }, [name])

can you update the code to use useMemo?

Exercise 21

More custom hooks

Hooks are powerful, but we can create our own hooks too 🔥

Let's say we want to build a hook that fetches posts and caches them

(This is going to not be a proper implementation - just a simple one for demo purchases!)

We've built a tool to let us "login" as a specific user.

(Our app is very secure...)

const posts = usePostsLoader(userId)

let's think about what we'd like our API to be.

 

Hooks are great for pulling out "boring" details and lets you not worry about how they work behind the scenes.

const usePostsLoader = userId => {
  const [postsCache, setPostsCache] = useState({})

  useEffect(() => {
    ...
  }, [userId])

  return postsCache[userId]
}

  

custom hooks can use other hooks internally!

postsCache: {
  1: [...],
  2: [...]
}

/* if we have the cache, return it
 * else make a network request.
 */

custom hooks can use other hooks internally!

const usePostsLoader = userId => {
  const [postsCache, setPostsCache] = useState({})

  useEffect(() => {
    if (!userId) return

    if (postsCache[userId]) {
      return
    } else {
      fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
        setPostsCache(cache => ({
          ...cache,
          [userId]: response.data,
        }))
      })
    }
  }, [userId, postsCache])

  return postsCache[userId]
}

the last line of a custom hook should be what the hook returns. This can be anything you want! 

const usePostsLoader = userId => {
  const [postsCache, setPostsCache] = useState({})

  useEffect(() => {
    if (!userId) return

    if (postsCache[userId]) {
      return
    } else {
      fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
        setPostsCache(cache => ({
          ...cache,
          [userId]: response.data,
        }))
      })
    }
  }, [userId, postsCache])

  return postsCache[userId]
}

your task: implement the usePostsLoader!

Exercise 22

Controlled vs uncontrolled components

An uncontrolled component controls its own state.

 A controlled component is told what its state is.

Why is this important?

Our app now lets us toggle between showing all posts, or just published posts.

we have an uncontrolled toggle component

const Toggle = props => {
  const [on, setOn] = useState(false)

  useEffect(() => {
    props.onToggleChange(on)
  }, [on, props])

  return <span onClick={() => setOn(o => !o)}>{on ? 'YES' : 'NO'}</span>
}

so in the main app we have the `publishedOnly` state

  const [publishedOnly, setPublishedOnly] = useState(false)


  Only published posts? <Toggle onToggleChange={setPublishedOnly} />

spot the problem here

  const [publishedOnly, setPublishedOnly] = useState(false)


  Only published posts? <Toggle onToggleChange={setPublishedOnly} />

the same state is in two places

 

this is a recipe for bugs!

lets make the component controlled

const Toggle = props => {

  return (
    <span onClick={() => props.onChange(!props.on)}>
      {props.on ? 'YES' : 'NO'}
    </span>
  )
}

no state in sight!

a prop tells us if we are on or not

we have a prop to tell our parent when we should change the state

lets make the component controlled

const Toggle = props => {

  return (
    <span onClick={() => props.onChange(!props.on)}>
      {props.on ? 'YES' : 'NO'}
    </span>
  )
}

no state in sight!

a prop tells us if we are on or not

we have a prop to tell our parent when we should change the state

lets make the component controlled

no state in sight!

a prop tells us if we are on or not

a prop to tell our parent when we should change the state

Exercise 23

More hooks

useRef

useRef to get at DOM elements

If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.

Autofocusing the login box

If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.

Autofocusing the login box

const inputEl = useRef(null)

Autofocusing the login box

<input
  type="text"
  ref={inputEl}
  value={loginName}
  placeholder="jack"
  onChange={e => setLoginName(e.target.value)}
/>

And how do we focus it? useEffect!

  useEffect(() => {
    if (inputEl.current && props.isShowing) {
      inputEl.current.focus()
    }
  }, [inputEl, props.isShowing])

Focus the input when I load the modal

  useEffect(() => {
    if (inputEl.current && props.isShowing) {
      inputEl.current.focus()
    }
  }, [inputEl, props.isShowing])

Exercise 24

useRef can hold onto any value

From the docs...

The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.

useEffect(() => {
  const timer = setTimeout(() => {
    setState(count => count + 1)
  }, 1000)

  return () => clearTimeout(timer)
})

Why is this useful?

normally when you clear out timers via useEffect, you do so by returning an unsubscribe function.

useEffect(() => {
  const timer = setTimeout(() => {
    setState(count => count + 1)
  }, 1000)

  return () => clearTimeout(timer)
})

But what if we want to allow the user to click to clear the timer?

how do we get at the timer ID from an event handler?

const countTimerId = useRef(null)

useEffect(() => {
  const timer = setTimeout(() => {
    setState(count => count + 1)
  }, 1000)

  countTimerId.current = timer

  return () => clearTimeout(timer)
})

But what if we want to allow the user to click to clear the timer?

we can store the timer ID in a ref!

const stopCounting = () => {
  clearTimeout(countTimerId.current)
}

But what if we want to allow the user to click to clear the timer?

we can store the timer ID in a ref!

Exercise 25

Extracting context usage into hooks

Hooks are great for hiding implementation details away.

const { loggedInUserName, setLoggedInUser } = useContext(AuthContext)

this doesn't feel like we're hiding things away.

const { loggedInUserName, setLoggedInUser } = useContext(AuthContext)

// compared to:

const { loggedInUserName, setLoggedInUser } = useAuth()

this feels better (fewer implementation details)

import { useContext } from 'react'
import AuthContext from './auth-context'

const useAuth = () => {
  const context = useContext(AuthContext)
  return context
}

export { useAuth }

let's use our useAuth hook

Exercise 26

Hiding more context details in the hook.

Hiding the creation of AuthContext

const AuthContext = createContext()

const useAuth = () => {
  const context = useContext(AuthContext)
  return context
}

We'll need to create a provider.

return (
    <div>
      <AuthContext.Provider value={authContextValue}>
        <JournalHeader />
        ...

we don't have this Provider available in our components now the context is created in use-auth.js

props.children

<MyCustomComponent>
  <p>hello world</p>
</MyCustomComponent>

you can refer to the given children as props.children within a component

 

this allows your custom component to take children and render them, but wrapped in something

our AuthProvider

1. Declare the initial value for the auth context
2. Have a piece of state that can be updated to update the context.
3. Wraps the children it is given in the AuthContext.Provider component.

our AuthProvider

const AuthProvider = props => {
  const [loggedInUserName, setLoggedInUserName] = useState('')

  const authContext = useMemo(() => {
    return {
      loggedInUserName,
      setLoggedInUserName,
    }
  }, [loggedInUserName])

  const { children, ...otherProps } = props

  return (
    <AuthContext.Provider value={authContext} {...otherProps}>
      {children}
    </AuthContext.Provider>
  )
}

And then wrap our app in this provider

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

let's walk through this in code

Exercise 27

there's no TODO here - we're going to walk through the code on screen and play with it.

Higher order components

Higher order functions

Higher order functions

const adder = x => y => x + y

const adder = function(x) {
  return function(y) {
    return x + y
  }
}

Higher order functions

const adder = x => y => x + y

const addTwo = adder(2)

addTwo(3) // 5

Higher order components

A higher order component is (slightly confusingly) a function that returns a React component.

Higher order components

Why is this useful?

Remember the AuthProvider from the previous exercise?

Rather than make the end user wrap their component in <AuthProvider />, we could provide a function that does this for them.

const wrapWithAuth = Component => {
  return Component
}
const wrapWithAuth = Component => {
  return Component
}


// usage:
const JournalWithAuth = wrapWithAuth(JournalApp)
const wrapWithAuth = Component => {
  const ComponentWrapped = props => {
  
  }

  return ComponentWrapped
}
const wrapWithAuth = Component => {
  const ComponentWrapped = props => {
     return (
       <AuthProvider>
         <Component {...props} />
       </AuthProvider>
     )
  }

  return ComponentWrapped
}
const wrapWithAuth = Component => {
  const ComponentWrapped = props => {
     return (
       <AuthProvider>
         <Component {...props} />
       </AuthProvider>
     )
  }

  return ComponentWrapped
}

Exercise 28

useReducer

Redux?

From the docs

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <Fragment>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </Fragment>
  );
}

A counter component in React

Don't reach for this too soon!

Don't assume you need this: start with useState and go for this if you then realise you need the structure.

One place where we have slightly more complex state is in our use-posts-loader

 

So let's update it to use a reducer internally.

const initialState = {}

const reducer = (state, action) => {

}

const usePostsLoader = userId => {
  const [postsCache, setPostsCache] = useState({})
  
  const [ state, dispatch ] = useReducer(reducer, initialState)
const reducer = (state, action) => {
  switch (action.type) {
    case 'newPostsForUser':
      return {
        ...state,
        [action.userId]: action.posts,
      }
  }
}

// usage:

dispatch({
  type: 'newPostsForUser',
  userId: 1,
  posts: [...]
})
const reducer = (state, action) => {
  switch (action.type) {
    case 'newPostsForUser':
      return {
        ...state,
        [action.userId]: action.posts,
      }
  }
}

// usage:

dispatch({
  type: 'newPostsForUser',
  userId: 1,
  posts: [...]
})
const reducer = (state, action) => {
  switch (action.type) {
    case 'newPostsForUser':
      return {
        ...state,
        [action.userId]: action.posts,
      }
  }
}

// usage:

dispatch({
  type: 'newPostsForUser',
  userId: 1,
  posts: [...]
})
  useEffect(() => {
    if (!userId) return

    if (state[userId]) {
      return
    } else {
      fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
        dispatch({
          type: 'newPostsForUser',
          userId: userId,
          posts: response.data,
        })
      })
    }
  }, [userId, state])
const usePostsLoader = userId => {
  const [postsCache, setPostsCache] = useState({})

  const [state, dispatch] = useReducer(reducer, initialState)

  useEffect(() => {
    ...
  }, [userId, state])

  return state[userId]
}

notice we now return from the redux state

And it works 🎉

Worth realising: we just entirely changed how we load and store our posts cache, and none of our components even know about it. This is one of the most powerful things about hooks.

Your turn ⬇

  useEffect(() => {
    if (!userId) return

    if (state[userId]) {
      return
    } else {
      fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
        dispatch({
          type: 'newPostsForUser',
          userId: userId,
          posts: response.data,
        })
      })
    }
  }, [userId, state])

Exercise 29

Lifting state up: reducer style.

User

Which user of our system is currently logged in.

Posts

The posts that we are showing to the given user.

these two bits of state are quite directly related.

So let's lift the state up

Our main component should house all this state.

We'll say goodbye to the use-posts-loader for now, but we can talk about how you'd bring it back once we've done the work.

We're also going to say goodbye to the useAuth context loader, and use the reducer state instead.

Both useReducer and useContext are useful tools

This exercise isn't me saying that I prefer useReducer over useContext, but showing you a different way you could solve the same problem. Each problem warrants a different solution - there is no direct rule on useReducer vs useContext

const initialState = {
  loggedInUser: {
    name: '',
  },
  postsForUser: [],
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'logUserIn':
      return {
        ...state,
        loggedInUser: {
          name: action.newUserName,
        },
      }

    case 'gotPostsForUser':
      return {
        ...state,
        postsForUser: action.posts,
      }

    default: {
      console.error(`Unknown action! ${action}`)
      return state
    }
  }
}
  useEffect(() => {
    const userId = userIdForName(state.loggedInUser.name)
    if (!userId) return

    fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
      dispatch({
        type: 'gotPostsForUser',
        posts: response.data,
      })
    })
  }, [state.loggedInUser.name])

we now move loading posts back into a useEffect block that dispatches an action

<JournalHeader loggedInUser={state.loggedInUser} dispatch={dispatch} />

and we now pass through the loggedIn state and the dispatch function to the JournalHeader

this is important! This is how you allow child components to update the state.

  const loggedInUserName = props.loggedInUser.name

  const setLoggedInUser = name => {
    props.dispatch({
      type: 'logUserIn',
      newUserName: name,
    })
  }

And within JournalHeader we can dispatch an action when someone wants to log in.

No exercise here, just have a play with the code locally.

What do you think are the pros and cons of useReducer at the top level like this?

 

Should our publishedOnly toggle state live in the reducer state? What do you think?

Exercise 30

Components are for UI, not for logic.

  const logInAs = username => event => {
    event.preventDefault()

    const currentTime = Date.now()

    const userId = userIdForName(username)

    fetch(`http://localhost:${apiPort}/users/${userId}`, {
      method: 'PUT',
      body: JSON.stringify({
        lastLogInTime: currentTime,
        name: username,
      }),
      headers: {
        'Content-Type': 'application/json',
      },
    })

    dispatch({
      type: 'logUserIn',
      newUserName: username,
    })
  }

Logging when a user logs in

Code that doesn't relate to the UI should not be in your React components.

  const logInAs = username => event => {
    event.preventDefault()

    const currentTime = Date.now()

    const userId = userIdForName(username)

    fetch(`http://localhost:${apiPort}/users/${userId}`, {
      method: 'PUT',
      body: JSON.stringify({
        lastLogInTime: currentTime,
        name: username,
      }),
      headers: {
        'Content-Type': 'application/json',
      },
    })

    dispatch({
      type: 'logUserIn',
      newUserName: username,
    })
  }

vs:

  const logInAs = username => event => {
    event.preventDefault()

    updateLastLoggedInTime(username);
    
    dispatch({
      type: 'logUserIn',
      newUserName: username,
    })
  }

Extract out the logging user in functionality

 

Where do you think it should go? What are our options?

Exercise 31

Choosing data structures that eliminate bugs.

Our journal lets us tag our posts.

But there's a bug!

  const onTagSubmit = event => {
    event.preventDefault()
    // you would make an API call here but we're ignoring this for now

    setTags(oldTags => [...oldTags, newTagValue])
    setNewTagValue('')
  }

Text

How do we fix it?

Add code to ensure that our array contains only unique items 🐛

 

Change our data structure to forbid duplicate values 🐛👌

What if we used a set?

const [tags, setTags] = React.useState(new Set(props.initialTags))

Exercise 32

PS: the MDN docs on Sets are very good 👌✅

GraphQL

GraphQL

One API endpoint. 

 

Describe what data you want, and get back exactly that.

 

No extra data that you don't need. One request for all the data your page requires.

query {
  users{
    id
    name
  }
} 
{
  "data": {
    "users": [
      {
        "id": "1",
        "name": "alice"
      },
      {
        "id": "2",
        "name": "bob"
      },
      {
        "id": "3",
        "name": "charlotte"
      }
    ]
  }
}
query {
  users{
    id
    name
    posts {
      title
    }
  }
} 
{
  "data": {
    "users": [
      {
        "id": "1",
        "name": "alice",
        "posts": [
          {
            "title": "Watched the new Game of Thrones!"
          },
          {
            "title": "Went to a cracking react workshop"
          }
        ]
      },
      {
        "id": "2",
        "name": "bob",
        "posts": [
          {
            "title": "Attended the React workshop"
          },
          {
            "title": "Played football in the park"
          },
          {
            "title": "Checked my credit score"
          },
          {
            "title": "Watched the 2018 world cup higlights again"
          }
        ]
      },
      {
        "id": "3",
        "name": "charlotte",
        "posts": [
          {
            "title": "did some tweeting"
          }
        ]
      }
    ]
  }
}

localhost:4000/graphql

(port might vary - please check the output of `npm start`)

Talking to GraphQL from React

 

It's just an HTTP request!

    fetch(`http://localhost:${graphqlPort}/graphql`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query: `
          query {
             posts {
                id
                title
                date
                body
                published
              }
            }
        `,
      }),
    }).then(result => {
      console.log('result', result.data)
    })

Posts per user and query variables

query postsForUser($id:ID!) {
  userById(id:$id){
    posts {
      id
      title
    }
  }
} 
    fetch(`http://localhost:${graphqlPort}/graphql`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query: `
          query postsForUser($id: ID!) {
            userById(id: $id) {
              posts {
                 id
                 title
                 date
                 body
                 published
               }
            }
          }
        `,
        variables: { id: userId },
      }),
    fetch(`http://localhost:${graphqlPort}/graphql`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query: `
          query postsForUser($id: ID!) {
            userById(id: $id) {
              posts {
                 id
                 title
                 date
                 body
                 published
               }
            }
          }
        `,
        variables: { id: userId },
      }),

Exercise 33

Could we create a useGraphQL hook?

 

What would that look like?

Advanced React: fin!

React Fundamentals 2019

By Jack Franklin

React Fundamentals 2019

  • 79

More from Jack Franklin