Build Youtube in React 10: adding routing in React

You already saw this this mockup in the last tutorial.

So the goal is clear. We would like to have a component where we can watch videos. Let’s get started.

2 Adding Watch component

Since our Watch component is likely to fetch data from the Youtube API endpoint to display additional info (amount of views, etc.), we put it into src/containers.

  1. Create a new directory inside src/containers and call it Watch
  2. Add a Watch.js and a Watch.scss file in the directory you’ve just created.

We will first add a few placeholders to get started.

import React from 'react';
import './Watch.scss';

export class Watch extends React.Component {
render() {
return (
<div>Watch component goes here</div>
);
}
}

But wait, hold on a second! When a user opens our website at localhost:3000, we want to show our Home feed first. We currently do that. But when do we show the Watch component?

Head over to Youtube and watch an arbitrary video, such as this one: https://www.youtube.com/watch?v=DqUQW3xyQ1c

Please have a look at the URL. Note that our URL has changed to /watch?v=DqUQW3xyQ1c. If you did not click the link above, your v query parameter will be different. In the above case the video id is DqUQW3xyQ1c.

In short: we need routing. Thanks to our refactoring in the last tutorial, this will be quite straight forward.

3 Adding React router to our create-react-app based application

React router is the de-facto standard routing library for React applications. Let’s go ahead an install it.

Since we are on the web, we install the react-router-dom package.

yarn add react-router-dom

This package will allow us to add navigation on our site, i.e. depending on our current URL, we will show different components. Note that the package’s name is react-router-dom and not react-router. There is a react-router package out there, but it just contains routing core components and is not sufficient to set up routing in your browser.

3.2. Wiring up React router in create-react-app

To navigate between pages in our application, we need to wrap it inside a <BrowserRouter> component that is provided by react-router-dom. That’s pretty much everything we have to do.

Head over to index.js in the root of your project and change it to:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'semantic-ui-css/semantic.min.css';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>, document.getElementById('root'));
registerServiceWorker();

If the user navigates to /, we show the Home feed. If the user navigates to /watch, we would like to show the Watch component.

Let’s do it.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'semantic-ui-css/semantic.min.css';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>, document.getElementById('root'));
registerServiceWorker();

We make use of a react-router's Switch component. The Switch component basically tells react-router: I want you to look at the URL and depending on what route where currently on, I want you to display a different component. The Switch component always displays just one component. This will always be the first component specified in a Route element where the path matches the current URL. Therefore, order matters! So if we were to switch line 11 and 12 in the above code snippet, we would always end up at our Home component because / matches every route.

Head over to your browser and go to localhost:3000. You should see the good old Home feed component.

Now navigate to localhost:3000/watch.

You should see this.

3.5. Adding routing to our logo

When you click the logo in the original Youtube application, you always get redirected to the Home feed. Now that we have routing in place, let’s implement this real quick. Head over to the src/containers/HeaderNav/HeaderNav.js file.

import {Link} from 'react-router-dom';
/* ... */
export class HeaderNav extends React.Component {
render() {
return (
<Menu borderless className='top-menu' fixed='top'>
{/* 2 */}
<Menu.Item header className='logo'>
<Link to='/'><Image src={logo} size='tiny'/></Link>
</Menu.Item>
{/*...*/}
</Menu>
);
}
}

We’re using the <Link> component from react-router-dom. The to prop specifies the destination of the link. In our case, we want to return to /.

4 Fixing broken tests

Let’s run our tests and see if something is broken.

yarn test

One thing you might have noticed is that the test in App.test.js fails with the following error message.

console.error node_modules/prop-types/checkPropTypes.js:19

Warning: Failed context type: The context `router` is marked as required in `Switch`, but its value is `undefined`.

in Switch (at App.js:11)

in div (at AppLayout.js:7)

in AppLayout (at App.js:10)

in App (at App.test.js:7)

console.error node_modules/react-dom/cjs/react-dom.development.js:14389 The above error occurred in the <Switch> component:

in Switch (at App.js:11)

in div (at AppLayout.js:7)

in AppLayout (at App.js:10) in App (at App.test.js:7)

Consider adding an error boundary to your tree to customize error handling behavior. Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

If you have a look at App.test.js, you see that we are actually trying to render the entire application. The issue here is that we are not rendering it to a browser’s DOM, but into a Javascript-based DOM (JSDOM). However, our BrowserRouter that we used in App.js only works with a browser's DOM, but not with our JSDOM.

Since we don’t want to render the entire app anyway, let’s do some cleanup work. Also note that currently our App.test.js file is not included in a __tests__ directory simply because it didn’t cause any issues so far.

  1. Create a __tests__ directory inside src
  2. Rename your App.test.js file to App.unit.test.js to stay consistent
  3. Move your App.unit.test.js file into the __tests__ directory you have just created
  4. Use a shallow rendering and a snapshot test to see if our App is rendered properly
import React from 'react';
import App from '../App';
import {shallow} from 'enzyme';

describe('App', () => {
test('renders', () => {
const wrapper = shallow(
<App/>
);
expect(wrapper).toMatchSnapshot();
});
});

Run your tests again using

yarn test

All tests should be green again.

5 Wrap Up

Congrats, you’ve just set up navigation in your application.

In the upcoming tutorials, we will break down the Watch component into smaller components and build them out step by step.

I didn’t want to start doing that here because then the tutorial gets too long.

As always, you can find this tutorial’s code on our Github repository.

Please follow me on Twitter @productioncoder to stay up-to-date. I’m happy to receive feedback of any kind.

If you are interested in more high-quality production-ready content, please enter your email below.

Originally published at productioncoder.com on January 26, 2019.

Building real-world apps on https://productioncoder.com