React (ui.dev) - pt 5

(Project) Tooltip

The code for this video can be found here.

The commit for this video can be found here.

(Project) withHover Higher Order Component

The code for this video can be found here.

The commit for this video can be found here.

Render Props

This post starts off the same as the “React Higher-order Components” section but soon changes to be Render Props focused.

There’s two important things to note before we get started. First, what we’re going to talk about is just a pattern. It’s not really even a React thing as much as it is a component architecture thing. Second, this isn’t required knowledge to build a React app. You could skip this post, never learn what we’re about to talk about, and still build fine React applications. However, just like building anything, the more tools you have available the better the outcome will be. If you write React apps, you’d be doing yourself a disservice by not having this in your “toolbox”.

You can’t get very far into studying software development before you hear the (almost cultish) mantra of Don't Repeat Yourself or D.R.Y . Sometimes it can be taken a bit too far, but for the most part, it’s a worthwhile goal. In this post we’re going to look at a pattern for accomplishing DRY in a React codebase, React Render Props. However before we can explore the solution, we must first fully understand the problem.

Let’s say we were in charge of recreating a dashboard similar to Stripe’s. As most projects go, everything goes great until the very end. Just when you think you’re about to be done, you notice that the dashboard has a bunch of different tooltips that need to appear when certain elements are hovered over.

There are a few ways to approach this. The one you decide to go with is to detect the hover state of the individual components and from that state, show or not show the tooltip. There are three components you need to add this hover detection functionality to - Info , TrendChart and DailyChart .

Let’s start with Info . Right now it’s just a simple SVG icon.

class Info extends React.Component {
  render() {
    return (
      <svg
        className="Icon-svg Icon--hoverable-svg"
        height={this.props.height}
        viewBox="0 0 16 16" width="16">
          <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
      </svg>
    )
  }
}

Now we need to add functionality to it so it can detect whether it’s being hovered over or not. We can use the onMouseOver and onMouseOut mouse events that come with React. The function we pass to onMouseOver will be invoked when the component is hovered over and the function we pass to onMouseOut will be invoked when the component is no longer being hovered over. To do this the React way, we’ll add a hovering state property to our component so that we can cause a re-render when the hovering state changes, showing or hiding our tooltip.

class Info extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.state.hovering === true
          ? <Tooltip id='info' />
          : null}
        <svg
          className="Icon-svg Icon--hoverable-svg"
          height={this.props.height}
          viewBox="0 0 16 16" width="16">
            <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
        </svg>
      </div>
    )
  }
}

Looking good. Now we need to add the same functionality to our other two components, TrendChart and DailyChart . If it’s not broke, don’t fix it. Our hover logic for Info worked great so let’s use that same code again.

class TrendChart extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.state.hovering === true
          ? <Tooltip id='trend' />
          : null}
        <Chart type='trend' />
      </div>
    )
  }
}

You probably know the next step. We can do the same thing for our final DailyChart component.

class DailyChart extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.state.hovering === true
          ? <Tooltip id='daily' />
          : null}
        <Chart type='daily' />
      </div>
    )
  }
}

And with that, we’re all finished. You may have written React like this before. It’s not the end of the world (#shipit), but it’s not very “DRY”. As you saw, we’re repeating the exact same hover logic in every one of our components.

At this point, the problem should be pretty clear, we want to avoid duplicating our hover logic anytime a new component needs it . So what’s the solution ? Well before we get to that, we need to get a refresh on two fundamental aspects of React. They are components which don’t render UI and passing functions as props.

No UI Components

In most cases whenever you build a React component, the end goal is to show some UI to the screen.

View = fn(state)

However, that doesn’t always need to be the case. It’s entirely reasonable to have components which act as “Wrapper” components. They’re responsible for handling some logic but instead of rendering their own UI, they just render another component passing it data.

class Users extends React.Component {
  state = {
    users: null
  }
  componentDidMount() {
    getUsers()
      .then((users) => {
        this.setState({ users })
      })
  }
  render() {
    <Grid data={this.state.users} />
  }
}

In the example above Users is responsible for getting the users, then passing them to the Grid component. It doesn’t have its own UI, instead, it uses the UI from the Grid component.

Passing functions as props

As you know, props are part of React’s component API that allow you to pass data into a component.

<User id='tylermcginnis' />

Then inside of the User component, the props object would have an id property referencing the string tylermcginnis .

function User (props) {
  const id = props.id // tylermcginnis
}

Now what if instead of passing a string as a prop, we passed a function?

<User id={() => 'tylermcginnis'} />

Now the props object still has an id property, only now instead of being a string, it references a function. So in order to get the id, we need to invoke the function.

function User (props) {
  const id = props.id() // tylermcginnis
}

Now what if we wanted to pass the function prop some data? Well, it’s just a function so we could do it just like we normally would by passing it an argument.

function User (props) {
  const id = props.id(true) // tylermcginnis
}

<User id={(isAuthed) => isAuthed === true ? 'tylermcginnis' : null} />

OK… but what do both of these have to do with the problem we saw earlier of duplicating our hover logic anytime a new component needs it? Well we can combine both of these simple concepts in order to solve our problem.

First, we want to create a “Wrapper” component which is responsible for managing the hover state. We’ll call it, naturally, Hover and it’ll contain all the hover logic that we had to duplicate from earlier.

class Hover extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>

      </div>
    )
  }
}

The next question becomes what should Hover render? This is where our function prop knowledge comes into play. Let’s have Hover receive a prop called render . This render prop is going to be a function that we can pass the hovering state to and it will return some UI.

<Hover render={(hovering) =>
  <div>
    Is hovering? {hovering === true ? 'Yes' : 'No'}
  <div>
} />

Now the last change we need to make is in our Hover component. All we need to do is invoke this.props.render passing it this.state.hover .

class Hover extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.props.render(this.state.hovering)}
      </div>
    )
  }
}

Well would you look at that. Now that we have our Hover component, any time we need a component to be aware of its hover state, we just wrap it inside of a Hover s render prop.

Finally let’s head back to the original code we had and see how we no longer have to duplicate all the hover logic since we have our Hover component.

This is what we had before.

class Info extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.state.hovering === true
          ? <Tooltip id='info' />
          : null}
        <svg
          className="Icon-svg Icon--hoverable-svg"
          height={this.props.height}
          viewBox="0 0 16 16" width="16">
            <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
        </svg>
      </div>
    )
  }
}

class TrendChart extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.state.hovering === true
          ? <Tooltip id='trend' />
          : null}
        <Chart type='trend' />
      </div>
    )
  }
}

class DailyChart extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.state.hovering === true
          ? <Tooltip id='daily' />
          : null}
        <Chart type='daily' />
      </div>
    )
  }
}

function App () {
  return (
    <>
      <Info />
      <TrendChart />
      <DailyChart />
    </>
  )
}

And now with our Hover component, instead of each component having to duplicate the hover logic, we can wrap each one inside of the render prop we pass to Hover and then pass down the hovering argument as a prop.

function Info (props) {
  return (
    <>
      {props.hovering === true
        ? <Tooltip id='info' />
        : null}
      <svg
        className="Icon-svg Icon--hoverable-svg"
        height={props.height}
        viewBox="0 0 16 16" width="16">
          <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
      </svg>
    </>
  )
}

function TrendChart (props) {
  return (
    <>
      {props.hovering === true
        ? <Tooltip id='trend' />
        : null}
      <Chart type='trend' />
    </>
  )
}


function DailyChart (props) {
  return (
    <>
      {props.hovering === true
        ? <Tooltip id='daily' />
        : null}
      <Chart type='daily' />
    </>
  )
}

function App () {
  return (
    <>
      <Hover render={(hovering) =>
        <Info hovering={hovering} />
      }/>

      <Hover render={(hovering) =>
        <TrendChart hovering={hovering} />
      }/>

      <Hover render={(hovering) =>
        <DailyChart hovering={hovering} />
      }/>
    </>
  )
}

This pattern, as you probably guessed by now, is called Render Props . Summarized in the React docs, “the term render prop refers to a technique for sharing code between React components using a prop whose value is a function”.

Another way to utilize the render props pattern is with React’s children prop. If you’ve never used props.children before, it’s just like any other prop, except instead of you passing it explicitly to the component, React automatically does it for you and it reference whatever is in the body of the component.

function User (props) {
  return (
    <div>
      {props.children}
    </div>
  )
}

<User>
  This is props.children
</User>

In the example above, what’s going to get rendered to the UI is a div with the words This is props.children inside of it.

Now what if instead of having props.children be a string, it was a function? Just as we saw earlier, we’d need to invoke it to get the value.

function User (props) {
  return (
    <div>
      {props.children()}    </div>
  )
}

<User>
  {() => This is props.children}</User>

With our newly formed knowledge of props.children , let’s update our examples from earlier. Now instead of Hover having a render prop, let’s get rid of that all together and use props.children instead.

function App () {
  return (
    <>
      <Hover>
        {(hovering) => <Info hovering={hovering} />}
      </Hover>

      <Hover>
        {(hovering) => <TrendChart hovering={hovering} />}
      </Hover>

      <Hover>
        {(hovering) => <DailyChart hovering={hovering} />}
      </Hover>
    </>
  )
}

Looks good. Now we need to update Hover so instead of invoking this.props.render , it invokes this.props.children .

class Hover extends React.Component {
  state = { hovering: false }
  mouseOver = () => this.setState({ hovering: true })
  mouseOut = () => this.setState({ hovering: false })
  render() {
    return (
      <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
        {this.props.children(this.state.hovering)}      </div>
    )
  }
}

Nice. Is this better? Not really, it’s just different. I prefer it, but there’s nothing objectively better about it.

If you read our post about Higher Order Components, you’ll be familiar with how HOCs have some pitfalls. The biggest one was with inversion of control and naming collisions. Because you have to pass your component over to the Higher-Order component, you have no control over how it’s rendered. We looked at an example with React Router’s withRouter HOC. withRouter will pass match , location , and history props to the wrapped component whenever it renders.

class Game extends React.Component {
  render() {
    const { match, location, history } = this.props // From React Router

    ...
  }
}

export default withRouter(Game)

If our Game component is already receiving match , location , or history as a prop, we’re going to have a naming collision and it’s going to be a hard bug to track down.

Does this same pitfall occur with Render Props? Nope. Instead of handing over the component, we hand over a function. Then, when that function is invoked, it’ll be passed the data we need. No inversion of control and no naming collisions since we can decide how the component is rendered.

<Hover>
  {(hovering) => {
    // We can do whatever we want here.
    // We decide how and when to render the component
    return <Info anyNameWeWant={hovering} />
  }}
</Hover>

Now the big question is, should you use Render Props or Higher Order Components? Well, that’s up to you. You now know how to use them both which means you have enough information to make an informed decision for yourself.

(Project) Hover Render Prop

The code for this video can be found here.

The commit for this video can be found here.

React Context

Whenever you’re utilizing a component architecture, as your application grows, the ability to share state amongst different components will inevitably become an issue.

Let’s pretend we had an app with the following architecture, each circle representing a different component.

Now let’s pretend that we had a piece of state that was needed throughout various levels of our application, represented here as a diamond.

The recommended solution for this problem is to move that state up to the nearest parent component and then pass it down via props.

This works, and most of the time it’s the right solution. However, there are times when passing props through intermediate components can become overly redundant or downright unmanageable. Take a tool like React Router for example. React Router needs to have the ability to pass routing props to any component in the component tree, regardless of how deeply nested the components are. Because this is such a significant problem, React comes with a built-in API to solve it called Context.

Context provides a way to pass data through the component tree without having to pass props down manually at every level. - The React Docs

Now that we know the problem that Context solves, how do we use it?

The Context API

For our example, let’s say we’re building an app that is used by both English and Spanish speaking countries. We want to expose a button that when it’s clicked, can toggle the text of our entire application between English and Spanish.

From a high level, if you think about what’s needed to solve this problem, there are two aspects to it.

  1. We need a way to declare the data that we want available throughout our component tree. In our example, that data is a locale value that will be either en or es .
  2. We need a way for any component in the component tree that requires that data to be able to subscribe to it.

React gives us the ability to do both of those things whenever we create a new Context using the React.createContext method.

Typically, you create a new Context for each unique piece of data that needs to be available throughout your component tree. Based on our example, we’ll create a LocaleContext .

const LocaleContext = React.createContext()

Now if we examine our LocaleContext , you’ll notice that it has two properties, both of which are React components, Provider , and Consumer .

Provider allows us to “declare the data that we want available throughout our component tree”.

Consumer allows “any component in the component tree that needs that data to be able to subscribe to it”.

Provider

You use Provider just like you would any other React component. It accepts a value prop which is the data that you want available to any of its children who need to consume it.

<MyContext.Provider value={data}>
  <App />
</MyContext.Provider>

In our example, we want locale to be available anywhere in the component tree. We also want to update the UI (re-render) whenever it changes, so we’ll stick it on our component’s state.

// LocaleContext.js
import React from "react"

const LocaleContext = React.createContext()

export default LocaleContext
import React from 'react'
import LocaleContext from './LocaleContext'

class App extends React.Component { 
  constructor(props) {
    super(props)

    this.state = {
      locale: 'en'
    }
  }
  render() {
    return (
      <LocaleContext.Provider value={this.state.locale}>
        <Home />
      </LocaleContext.Provider>
    )
  }
}

export default App

Now, any component in our component tree that needs the value of locale will have the option to subscribe to it using LocaleContext.Consumer .

Consumer

Again, the whole point of the Consumer component is it allows you to get access to the data that was passed as a value prop to the Context’s Provider component. To do this, Consumer uses a render prop.

<MyContext.Consumer>
  {(data) => {
    return (
      <h1>
        The "value" prop passed to "Provider" was {data}
      </h1>
    )
  }}
</MyContext.Consumer>

Now in our example, because we passed this.state.locale as the value prop to LocaleContext.Provider , we can get access to it by passing LocaleContext.Consumer a render prop.

// Blog.js
import React from 'react'
import LocaleContext from './LocaleContext'

export default function Blog () {
  return (
    <LocaleContext.Consumer>
      {(locale) => <Posts locale={locale} />}
    </LocaleContext.Consumer>
  )
}

Updating Context State

At this point, we’ve seen that because we wrapped our whole app in <LocaleContext.Provider value={this.state.locale}> , any component in our application tree can get access to locale by using LocaleContext.Consumer . However, getting access to locale isn’t enough. We also want to be able to toggle it ( en -> es ) from anywhere inside of our component tree. So just as we did with this.state.locale , we’ll add a new property to the value prop of LocaleContext.Provider .

Your first intuition might be to do something like this.

class App extends React.Component { 
  constructor(props) {
    super(props)

    this.state = {
      locale: 'en'
    }
  }
  render() {
    return (
      <LocaleContext.Provider 
        value={{
          locale: this.state.locale,
          toggleLocale: : () => {
            this.setState(({ locale }) => ({
              locale: locale === "en" ? "es" : "en"
            }));
          }
        }}>
          <Home />
      </LocaleContext.Provider>
    )
  }
}

So what we’ve done is added a new property to the object we pass to value . Now, anywhere in our component tree, using LocaleContext.Consumer , we can grab locale OR toggleLocale .

Sadly, the idea is right, but the execution is wrong. Can you think of any downsides to this approach? Hint, it has to do with performance.

Just like React re-renders with prop changes, whenever the data passed to value changes, React will re-render every component which used Consumer to subscribe to that data. The way in which React knows if the data changes is by using “reference identity” (which is kind of a fancy way of saving oldObject === newObject ).

Currently, how we have it set up ( value={{}} ), we’re passing a new object to value every time that App re-renders. What this means is that when React checks if the data passed to value has changed, it’ll always think it has since we’re always passing in a new object. As a result of that, every component which used Consumer to subscribe to that data will re-render as well, even if nothing changed.

To fix this, instead of passing a new object to value every time, we want to give it a reference to an object it already knows about. Though it may look weird, our state object fits this need perfectly.

class App extends React.Component { 
  constructor(props) {
    super(props)

    this.state = {
      locale: 'en',
      toggleLocale: () => {
        this.setState(({ locale }) => ({
          locale: locale === "en" ? "es" : "en"
        }));
      }
    }
  }
  render() {
    return (
      <LocaleContext.Provider value={this.state}>
        <Home />
      </LocaleContext.Provider>
    )
  }
}

Now, anywhere inside of our component tree, we can get access to the locale value or the ability to change it via toggleLocale .

// Blog.js
import React from 'react'
import LocaleContext from './LocaleContext'

export default function Blog () {
  return (
    <LocaleContext.Consumer>
      {({ locale, toggleLocale }) => (
        <React.Fragment>
          <Nav toggleLocal={toggleLocale} />
          <Posts locale={locale} />
        </React.Fragment>
      )}
    </LocaleContext.Consumer>
  )
}

Here’s the link to the full locale app if you want to play around with it. Admittedly, it’s not the best use of Context as it’s a pretty shallow app, but it gives you the general idea how to use Context in an app with multiple routes/components.

defaultValue

Whenever you render a Consumer component, it gets its value from the value prop of the nearest Provider component of the same Context object. However, what if there isn’t a parent Provider of the same Context object? In that case, it’ll get its value from the first argument that was passed to createContext when the Context object was created.

const MyContext = React.creatContext('defaultValue')

And adapted to our example.

const LocaleContext = React.createContext('en')

Now, if we use <LocaleContext.Consumer> without previously rendering a <LocaleContext.Provider> , the value passed to Consumer will be en .

Here’s a very clever example my good friend chantastic came up with. I’ve modified it a bit, but the core idea is his.

Here’s a live version you can play around with.

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

const ExpletiveContext = React.createContext('shit')

function ContextualExclamation () {
  return (
    <ExpletiveContext.Consumer>
      {(word ) => <span>Oh {word}!</span>}
    </ExpletiveContext.Consumer>
  )
}

function VisitGrandmasHouse () {
  return (
    <ExpletiveContext.Provider value='poop'>
      <h1>Grandma's House 🏡</h1>
      <ContextualExclamation />
    </ExpletiveContext.Provider>
  )
}

function VisitFriendsHouse () {
  return (
    <React.Fragment>
      <h1>Friend's House 🏚</h1>
      <ContextualExclamation />
    </React.Fragment>
  )
}

function App () {
  return (
    <React.Fragment>
      <VisitFriendsHouse />
      <VisitGrandmasHouse />
    </React.Fragment>
  )
}

Can you follow what’s going on? First, we create a new ExpletiveContext and set its default value to shit . Then we render two components, VisitFriendsHouse and VisitGrandmasHouse .

Because we’re allowed to swear at our friend’s house, VisitFriendsHouse renders ExpletiveContext.Consumer whose value will default to shit since there’s not a ExpletiveContext.Provider in the tree above it.

Unlike at our friends, with Grandma, we’re not allowed to swear. So instead of just rendering ExpletiveContext.Consumer , we wrap it in ExpletiveContext.Provider passing it a value of poop . This way when the Consumer looks for its nearest Provider , it’ll find it and get a value of poop rather than the default value of shit .

Warnings

Here’s the thing, when you’re a hammer, everything looks like a nail. Typically when you first learn about Context, it appears like it’s the solution to all your problems. Just remember, there’s nothing wrong with passing props down multiple levels, that’s literally how React was designed. I don’t have a universal rule for when you should and shouldn’t use Context, just be mindful that it’s common to overuse it.

(Project) Theme Provider

The code for this video can be found here.

The commit for this video can be found here.

(Project) Toggle Theme

The code for this video can be found here.

The commit for this video can be found here.

(Project) Consume Theme

The code for this video can be found here.

The commit for this video can be found here.

Introduction to React Router v4

This is originally part of our React Router v4 course. However, it’s applicable to us here as well.

If you’ve been in React land for the last few years, you know that React Router has gone through a few different iterations. Say what you will, but it’s clear that the React Router we have today is a huge improvement on previous versions. The reason for these changes are pretty standard - the authors today are more experienced React developers than they were when React Router was first built. You see, back in 2014, everyone was new to React. React itself was still under a year old and no one really knew to what extent this whole component thing would play out. With that in mind, it’s natural that the first commits of React Router looked something like this.

At the time, the creators of React Router were coming from Ember backgrounds. So naturally, the first version of React Router was similar in nature to that of Ember’s router. That is, with both routers you’d establish your routes statically as part of the app’s initialization process. In fact, mostly all of the router’s you’re probably familiar with are used this way - Express, Angular, Ember, etc.

Here’s some code from an older version of React Router (v3). As you can see, you’d have a routes.js file where you’d establish your static routes.

// routes.js

const routes = (
  <Router>
    <Route path='/' component={Main}>
      <IndexRoute component={Home} />
      <Route path='playerOne' component={Prompt} />
      <Route path='playerTwo/:playerOne' component={Prompt} />
      <Route path='battle' component={ConfirmBattle} />
      <Route path='results' component={Results} />
      <Route onEnter={checkAuth} path='dashboard' component={Dashboard} />
    </Route>
  </Router>
)

export default routes

Then, when you’d initialize your app, you’d import and render your routes.

// index.js

import React from 'react'
import ReactDOM from 'react-dom'
import routes from './config/routes'

ReactDOM.render(routes, document.getElementById('app'))

This brings up the question, “is static routing bad?”. The answer to that is obviously no. However, one could argue it’s not really the “React way” of doing things. Since its creation, as the creators of React Router gained more experience, what they found was the original API behind React Router didn’t align with the mental model of React. Not only that but in some places React Router actually competed with the React API.

Looking back at the previous example, we pass an onEnter prop to the <Route> component.

<Route
  onEnter={checkAuth}
  path='dashboard'
  component={Dashboard}
/>

The idea here is that before the user sees the Dashboard component, the checkAuth function verifies the user is authenticated. Well, doesn’t that sound similar to what should happen inside of Dashboard 's componentDidMount lifecycle hook? It is.

With the original versions of React Router, it was more of a router for React rather than a true React router.

React Router v4 was built to fix these inconsistencies and work with React, rather than against it. If you’re already familiar with the benefits of React and the benefits of component composition, React Router v4 is going to make you feel at home - you just need to forget everything you know about traditional static routers.

Now the question is why is it that React Router v4 aligns nicely with React when previous versions fought against it? The answer is because it ditched static routing in favor of dynamic routing and the entire API is just components. What that means is that you declare your routes as part of your application just like you would any other component.

Let’s take a look at some code.

The goal here is to start out with some very basic code, then slowly add routing functionality to it. Here’s our starting code.

import React from 'react'

class App extends React.Component {
  render() {
    return (
      <div>
        React Router Course
      </div>
    )
  }
}

export default App

As I mentioned earlier, React Router v4 is “just components”. So the first thing we’ll need to do is import the components we’ll need.

import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'

A few things to note here. First, we’re importing BrowserRouter and renaming it Router . That’s not necessary, but it’s pretty common. What BrowserRouter does is it allows React Router to pass the app’s routing information down to any child component it needs (via context). So to make React Router work, you’ll need to render BrowserRouter at the root of your application.

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          React Router Course
        </div>
      </Router>
    )
  }
}

export default App

Next, we have Route . Route is both the backbone and the genius behind React Router v4. When the app’s location matches a certain path, Route will render a specified component, when it doesn’t, it will render null. So say, for example, we had a Home component that we wanted to render when our app was at the index path / . Our code would look something like this

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'

const Home = () => (
  <h2>Home</h2>
)

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={Home} />
        </div>
      </Router>
    )
  }
}

export default App

:computer: Play with the code.

With the code above, if we were at the index page (/), we would see the Home component. If we weren’t, we wouldn’t see anything (because Route would have rendered null ).

Now, what if we wanted to add some more routes to our app? All we need to do is render more Route components.

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)

const About = () => (  <div>    <h2>About</h2>  </div>)
const Topics = () => (  <div>    <h2>Topics</h2>  </div>)
class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={Home} />
          <Route path='/about' component={About} />          <Route path='/topics' component={Topics} />        </div>
      </Router>
    )
  }
}

export default App

:computer: Play with the code.

Again, this may feel a little weird to your brain if you’re coming from static based routers since we’re literally rendering our routes. One thing that helped me was to remember Route is just a normal React component with a render method. That render method is either rendering the component or it’s rendering null depending on if the path matches. So when we render multiple Route components like we’re doing above, those will either render the component or render null .

So far, so good. One caveat that you might not have seen from the above code is that right now if you run the app and you head to the /about path, you’ll notice that both the About component and the Home component are rendered. This is because even though / doesn’t match the location exactly, it’s still considered a partial match so the Home component is rendered. To get around this, you need to add an exact prop to the / Route to specify that you only want it to match when the location matches exactly.

<Route exact path='/' component={Home} />

:computer: Play with the code.

Now that we’re dynamically rendering UI based on the app’s location, the next thing we need to do is have some way for the user to change the app’s location. This is where the Link component comes into play. It’s a simple component that allows the user to declaratively navigate around the app. Now, using Link , let’s add a simple navbar to our app.

render() {
  return (
    <Router>
      <div>
        <ul>
          <li><Link to='/'>Home</Link></li>
          <li><Link to='/about'>About</Link></li>
          <li><Link to='/topics'>Topics</Link></li>
        </ul>

        <Route exact path='/' component={Home} />
        <Route path='/about' component={About} />
        <Route path='/topics' component={Topics} />
      </div>
    </Router>
  )
}

:computer: Play with the code.

At this point, we’ve covered the absolute fundamentals of React Router v4. We’re dynamically changing the UI based on the location by rendering a few different Route components and we’re able to change the location of our app by rendering a few different Link components. Let’s go a little deeper and talk about nested routes.

Nested routes were a fundamental aspect of previous versions of React Router and they continue to be today. The biggest difference is how you go about creating nested routes now compared to previous versions of React Router (<= v3). In previous, static versions, you’d nest routes in your route config. Because React Router v4 is all about dynamic routing, you can’t do that. However, in my opinion, nested routes with React Router v4 is much more intuitive than with previous, static versions. Again, the key is to forget what you knew previously.

Looking back at our example, what if we wanted the Topics component to render a nested navbar and some nested routes? The answer to that doesn’t need to be complicated. Just like you would nest a div, you can nest Route s.

const Topic = () => (
  <div>
    <h3>TOPIC</h3>
  </div>
)

const Topics = () => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`/topics/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`/topics/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`/topics/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>

    <Route path={`/topics/rendering`} component={Topic} />
    <Route path={`/topics/components`} component={Topic} />
    <Route path={`/topics/props-v-state`} component={Topic} />
  </div>
)

:computer: Play with the code.

Now when the user navigates to /topics , they’ll see a nested navbar and the UI will dynamically change - just like before - based on the location. The only difference is now we’re rendering the navbar and the Route s inside of another component, which is also being rendered by React Router.

You may have noticed that we hardcoded the URLs instead of dynamically creating them based on the current nested location we’re on.

<Route path={`/topics/rendering`} component={Topic} />
<Route path={`/topics/components`} component={Topic} />
<Route path={`/topics/props-v-state`} component={Topic} />

When React Router renders a component, it passes that component three things: match , location , and history . In this example, what we want is match.url which will give us the current matched portion of the URL (in our example, /topics ). So anywhere where we’re hard-coding /topic , we can replace with match.url .

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>          Props v. State
        </Link>
      </li>
    </ul>

    <Route path={`${match.url}/rendering`} component={Topic} />    <Route path={`${match.url}/components`} component={Topic} />    <Route path={`${match.url}/props-v-state`} component={Topic} />  </div>
)

:computer: Play with the code.

Another thing you may have noticed is that we’re rendering three different Route s even though each are rendering the same component and the only difference is the nested URL.

<Route path={`${match.url}/rendering`} component={Topic} />
<Route path={`${match.url}/components`} component={Topic} />
<Route path={`${match.url}/props-v-state`} component={Topic} />

This is the perfect use case for using URL parameters instead.

const Topics = ({ match }) => (
  <div>
    ...

    <Route path={`${match.url}/:topicId`} component={Topic} />
  </div>
)

Now when React Router renders the Topic component, because we’re passed that match prop we talked about earlier, we’re also passed the topicId under match.params .

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)

:computer: Play with the code.

Now, lastly, when we’re at the /topics route, if a topic hasn’t already been selected, we want to render some text that says “Please select a topic”. We can tell if a topic has been selected by looking at the URL. If the URL is exactly /topics , a topic hasn’t been selected. As we saw earlier, we can use the exact prop to tell React Router to only match if the path matches exactly.

If you just want to render something inline without creating a component, you can use Route s render prop.

<Route exact path={match.url} render={() => (
  <h3>Please select a topic.</h3>
)}/>

:computer: Play with the code.

That’s it! Our final code now looks like this,

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)

const About = () => (
  <div>
    <h2>About</h2>
  </div>
)

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>

    <Route path={`${match.url}/:topicId`} component={Topic} />
    <Route exact path={match.url} render={() => (
      <h3>Please select a topic.</h3>
    )} />
  </div>
)

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <ul>
            <li><Link to='/'>Home</Link></li>
            <li><Link to='/about'>About</Link></li>
            <li><Link to='/topics'>Topics</Link></li>
          </ul>

          <Route exact path='/' component={Home} />
          <Route path='/about' component={About} />
          <Route path='/topics' component={Topics} />
        </div>
      </Router>
    )
  }
}

export default App

:computer: Play with the code.

By utilizing a component-based API, React Router v4 truly is a React router. I believe React will make you a better JavaScript developer and React Router v4 will make you a better React developer.