React Projects - Second Edition

By Roy Derks
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    Chapter 2: Creating a Portfolio in React with Reusable Components and Routing
About this book

Developed by Facebook, React is a popular library for building impressive user interfaces. React extends its capabilities to mobile platforms using the React Native framework and integrates with popular web and mobile tools to build scalable applications.

React Projects is your guide to learning React development by using modern development patterns and integrating React with powerful web tools, such as GraphQL, Expo, and React 360. You'll start building a real-world project right from the first chapter and get hands-on with developing scalable applications as you advance to building more complex projects. Throughout the book, you'll use the latest versions of React and React Native to explore features such as routing, Context, and Hooks on multiple platforms, which will help you build full-stack web and mobile applications efficiently. Finally, you'll get to grips with unit testing with Jest and end-to-end testing with Cypress to build test-driven apps.

By the end of this React book, you'll have developed the skills necessary to start building scalable React apps across web and mobile platforms.

Publication date:
April 2022
Publisher
Packt
Pages
384
ISBN
9781801070638

 

Chapter 2: Creating a Portfolio in React with Reusable Components and Routing

Do you already feel familiar with React's core concepts after completing the first chapter? Great! This chapter will be no problem for you! If not, don't worry – most of the concepts you came across in the previous chapter will be repeated. However, if you want to get more experience with Webpack and Babel, it's recommended that you try creating the project in Chapter 1, Creating a Single-Page Application in React, again since this chapter won't be covering those topics.

In this chapter, you'll work with Create React App, a starter kit created by the React core team to get you started with React quickly. It will make the configuration of module bundlers and compilers such as Webpack and Babel unnecessary, as this will be taken care of by the Create React App package. This means you can focus on building your portfolio application, which reuses React components and has routing. Besides that, we'll be adding routing using react-router v6, which is the leading library for routing in React.

Alongside setting up Create React App, the following topics will be covered in this chapter:

  • Creating a new project with Create React App
  • Building reusable React components
  • Routing with react-router

Can't wait? Let's go!

 

Project overview

In this chapter, we will create an application with React that makes use of reusable React components and styling using Create React App and styled-components. The application will use data that is fetched from the public GitHub API.

The build time is 1.5–2 hours.

 

Getting started

The project you'll create in this chapter will use the public API from GitHub, which you can find at https://docs.github.com/en/rest. To use this API, you need to have a GitHub account, since you'll want to retrieve information from a GitHub user account. If you don't have a GitHub account yet, you can create one on the GitHub website. The complete source code for this application can also be found on GitHub: https://github.com/PacktPublishing/React-Projects-Second-Edition/tree/main/Chapter02.

 

Creating a portfolio in React

In this section, we will learn how to create a new React project using Create React App and add reusable React components and routing with react-router.

Creating a portfolio with Create React App

Having to configure Webpack and Babel every time we create a new React project can be quite time-consuming. Also, the settings for every project can change, and it becomes hard to manage all of these configurations when we want to add new features to our project.

Therefore, the React core team introduced a starter kit known as Create React App, which is currently at version 5. By using Create React App, we no longer have to worry about managing compile and build configurations, even when newer versions of React are released, which means we can focus on coding instead of configurations.

This section will show us how to create a React application with Create React App.

Before anything else, let's see how to install Create React App.

Installing Create React App

Create React App doesn't have to be installed globally. Instead, we can use npx, a tool that comes preinstalled with npm (v5.2.0 or higher) and simplifies the way that we execute npm packages:

npx create-react-app chapter-2

This will start the installation process for Create React App, which can take several minutes, depending on your hardware. Although we're only executing one command, the installer for Create React App will install the packages we need to run our React application. Therefore, it will install react, react-dom, and react-scripts, where the last package includes all the configurations for compiling, running, and building React applications.

If we move into the project's root directory, which is named after our project name, we will see that it has the following structure:

chapter-2
  |- node_modules
  |- package.json
  |- public
     |- index.html
  |- src
     |- App.css
     |- App.test.js
     |- App.js
     |- index.css
     |- index.js

Note

Not all files that were created by Create React App are listed; instead, only the ones used in this chapter are listed.

This structure looks a lot like the one we set up in the first chapter, although there are some slight differences. The public directory includes all the files that shouldn't be included in the compile and build process, and the files inside this directory are the only files that can be directly used inside the index.html file.

In the other directory, called src, we will find all the files that will be compiled and built when we execute any of the scripts inside the package.json file. There is a component called App, which is defined by the App.js, App.test.js, and App.css files, and a file called index.js, which is the entry point for Create React App.

If we open the package.json file, we'll see that four scripts have been defined: start, build, test, and eject. Since the last two aren't handled at this point, we can ignore these two scripts for now. To be able to open the project in the browser, we can simply type the following command into the command line, which runs package react-scripts in development mode:

npm start

Note

Instead of npm start, we can also run yarn start, as using Yarn is recommended by Create React App.

If we visit http://localhost:3000/, the default Create React App page will look as follows:

Figure 2.1 – The default Create React App boilerplate

Figure 2.1 – The default Create React App boilerplate

Since react-scripts supports hot reloading by default, any changes we make to the code will result in a page reload. If we run the build script, a new directory called build will be created in the project's root directory, where the minified bundle of our application can be found.

With the basic installation of Create React App in place, we will start looking at creating the components for our project and styling them.

Building reusable React components

Creating React components with JSX was briefly discussed in the previous chapter, but in this chapter, we'll explore this topic further by creating components that we can reuse throughout our application. First, let's look at how to structure our application, which builds upon the contents of the previous chapter.

Structuring our application

Our project still consists of only one component, which doesn't make it very reusable. To begin, we'll need to structure our application in the same way that we did in the first chapter. This means that we need to split up the App component into multiple smaller components. If we look at the source code for this component in App.js, we'll see that there's already a CSS header element in the return function. Let's change that header element into a React component:

  1. First, create a new file called Header.css inside a new directory called components within src and copy the styling for classNames, App-header, App-logo, and App-link into it:
    .App-logo {
      height: 40vmin;
      pointer-events: none;
    }
    @media (prefers-reduced-motion: no-preference) {
      .App-logo {
        animation: App-logo-spin infinite 20s linear;
      }
    }
    .App-header {
      background-color: #282c34;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      font-size: calc(10px + 2vmin);
      color: white;
    }
    .App-link {
      color: #61dafb;
    }
    @keyframes App-logo-spin {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
  2. Now, create a file called Header.js inside this directory. This file should return the same content as the <header> element:
    import './Header.css';
    function Header() {
      return (
        <header className='App-header'>
          <img src={logo} className='App-logo' alt='logo'
          />
          <p>Edit <code>src/App.js</code> 
            and save to reload. </p>
          <a
            className='App-link'
            href='https://reactjs.org'
            target='_blank'
            rel='noopener noreferrer'
          >
            Learn React
          </a>
        </header>
      );
    }
    export default Header;
  3. Import this Header component inside your App component and add it to the return function:
    + import Header from './components/Header';
      import './App.css';
      import logo from './logo.svg';
      function App() {
        return (
          <div className="App">
    -       <header className='App-header'>
    -        <img src={logo} className='App-logo'
               alt='logo' />
    -        <p>Edit <code>src/App.js</code> and save to
               reload. </p>
    -        <a
    -          className='App-link'
    -          href='https://reactjs.org'
    -          target='_blank'
    -          rel='noopener noreferrer'
    -        >
    -         Learn React
    -       <a>
    -     </header>
    +     <Header />
        </div>
      );
    }
    export default App;

The styles for the header need to be deleted from App.css. This file should only contain the following style definitions:

.App { 
  text-align: center; 
} 
.App-link { 
  color: #61dafb; 
}

When we visit our project in the browser again, we'll see an error saying that the value for the logo is undefined. This is because the new Header component can't reach the logo constant that's been defined inside the App component. From what we learned in the first chapter, we know that this logo constant should be added as a prop to the Header component so that it can be displayed. Let's do this now:

  1. Send the logo constant as a prop to the Header component in src/App.js:
    // ...
    function App() {
       return (
         <div className='App'>
    -      <Header />
    +      <Header logo={logo} />
         </div>
       );
     }
    }
    export default App;
  2. Get the logo prop so that it can be used by the img element as an src attribute in src/components/Header.js:
      import './Header.css';
    - function Header() {
    + function Header({ logo }) {
        return (
          <header className='App-header'>
            // ...

Here, we won't see any visible changes when we open the project in the browser. But if we open up the React Developer Tools, we will see that the project is now divided into an App component and a Header component. This component receives the logo prop in the form of a .svg file, as shown in the following screenshot:

Figure 2.2 – The React Developer Tools

Figure 2.2 – The React Developer Tools

The Header component is still divided into multiple elements that can be split into separate components. Looking at the img and p elements, they look pretty simple already. However, the a element looks more complicated and takes attributes such as url, title, and className. To change this a element into a component we can reuse, it needs to be moved to a different location in our project.

To do this, create a new file called Link.js inside the components directory. This file should return the same a element that we've already got inside our Header component. Also, we can send both url and title to this component as a prop. Let's do this now:

  1. Delete the styling for the App-link class from src/components/Header.css and place this inside a file called Link.css:
    .App-link {
        color: #61dafb;
    }
  2. Create a new component called Link that takes the url and title props. This component adds these props as attributes to the <a> element in src/components /Link.js:
    import './Link.css';
    function Link({ url, title }) {
      return (
        <a
          className='App-link'
          href={url}
          target='_blank'
          rel='noopener noreferrer'
        >
          {title}
        </a>
      );
    };
    export default Link;
  3. Import this Link component and place it inside the Header component in src/components/Header.js:
    + import Link from './Link.js';
      import './Header.css';
      function Header({ logo }) {
        return (
          <header className='App-header'>
            <img src={logo} className='App-logo'
              alt='logo' />
            <p>Edit <code>src/App.js</code> and save to 
              reload. </p>
    -       <a
    -         className='App-link'
    -         href='https://reactjs.org'
    -         target='_blank'
    -         rel='noopener noreferrer'
    -        >
    -          Learn React
    -       <a>
    +       <Link 
    +         url='https://reactjs.org'
    +         title='Learn React' 
    +        />
        </header>
      );
    }
    export default Header;
  4. Our code should now look like the following, meaning that we've successfully split the App component into different files in the components directory. Also, the logo.svg file can be moved to a new directory called assets:
    chapter-2
      |- node_modules
      |- package.json
      |- public
         |- index.html
      |- src
         |- assets
            |- logo.svg
         |- components
            |- Header.css
            |- Header.js
            |- Link.css
            |- Link.js
         |- App.css
         |- App.js 
         |- index.css
         |- index.js
  5. Don't forget to also change the import statement in the src/App.js file, where the logo.svg file is imported as a component:
      import Header from './components/Header';
      import './App.css';
    - import logo from './logo.svg';
    + import logo from './assets/logo.svg';
      function App() {
        return (
          // ...

However, if we take a look at the project in the browser, no visible changes are present. In the React Developer Tools, however, the structure of our application has already taken shape. The App component is shown as the parent component in the component tree, while the Header component is a child component that has Link as a child.

In the next part of this section, we'll add more components to the component tree of this application and make these reusable throughout the application.

Reusing components in React

The project we're building in this chapter is a portfolio page; it will show our public information and a list of public repositories. Therefore, we need to fetch the official GitHub REST API (v3) and pull information from two endpoints. Fetching data is something we did in the first chapter, but this time, the information won't come from a local JSON file. The method to retrieve the information is almost the same. We'll use the fetch API to do this.

We can retrieve our public GitHub information from GitHub by executing the following command (replace username at the end of the bold section of code with your own username):

curl 'https://api.github.com/users/username'

Note

If you don't have a GitHub profile or haven't filled out all the necessary information, you can also use the octocat username. This is the username of the GitHub mascotte and is already filled with sample data.

This request will return the following output:

{
  "login": "octocat",
  "id": 583231,
  "node_id": "MDQ6VXNlcjU4MzIzMQ==",
  "avatar_url": 
    "https://avatars.githubusercontent.com/u/583231?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/octocat",
  "html_url": "https://github.com/octocat",
  "followers_url":
    "https://api.github.com/users/octocat/followers",
  "following_url":
    "https://api.github.com/users/octocat/following{
      /other_user}",
  "gists_url": 
    "https://api.github.com/users/octocat/gists{/gist_id}",
  "starred_url":
    "https://api.github.com/users/octocat/starred{/owner}{
      /repo}",
  "subscriptions_url": 
    "https://api.github.com/users/octocat/subscriptions",
  "organizations_url":
    "https://api.github.com/users/octocat/orgs",
  "repos_url":
    "https://api.github.com/users/octocat/repos",
  "type": "User",
  "site_admin": false,
  "name": "The Octocat",
  "company": "@github",
  "blog": "https://github.blog",
  "location": "San Francisco",
  "email": null,
  "hireable": null,
  "bio": null,
  "twitter_username": null,
  "public_repos": 8,
  "public_gists": 8,
  "followers": 3555,
  "following": 9
}

Multiple fields in the JSON output are highlighted, since these are the fields we'll use in the application. These are avatar_url, html_url, repos_url, name, company, location, email, and bio, where the value of the repos_url field is actually another API endpoint that we need to call to retrieve all the repositories of this user. This is something we'll do later in this chapter.

Since we want to display this result in the application, we need to do the following:

  1. To retrieve this public information from GitHub, create a new component called Profile inside a new directory called pages. This directory will hold all the components that represent a page in our application later on. In this file, add the following code to src/pages/Profile.js:
    import { useState, useEffect } from 'react';
    function Profile({ userName }) {
      const [loading, setLoading] = useState(false);
      const [profile, setProfile] = useState({});
      useEffect(() => {
        async function fetchData() {
          const profile = await fetch(
            'https://api.github.com/users/${userName}');
          const result = await profile.json();
          if (result) {
            setProfile(result);
            setLoading(false);
          }
        }
        fetchData();
      }, [userName]);
      return (
        <div>
          <h2>About me</h2>
          {loading ? (
            <span>Loading...</span>
          ) : (
            <ul></ul>
          )}
        </div>
       );
    }
    export default Profile;

This new component imports two Hooks from React, which are used to handle state management and life cycles. We've already used a useState Hook in the previous chapter, and it's used to create a state for loading and profile. Inside the second Hook, which is the useEffect Hook, we do the asynchronous data fetching from the GitHub API. No result has been rendered yet, since we still need to create new components to display the data.

  1. Now, import this new component into the App component and pass the userName prop to it. If you don't have a GitHub account, you can use the username octocat:
      import Header from './Header';
    + import Profile from './pages/Profile';
      import './App.css';
      function App() {
        return (
          <div className='App'>
            <Header logo={logo} />
    +       <Profile userName="octocat" />
          </div>
        );
      }
    }
    export default App;
  2. A quick look at the browser where our project is running shows that this new Profile component isn't visible yet. This is because the Header.css file has a height attribute with a view-height value of 100, meaning that the component will take up the entire height of the page. To change this, open the src/components/Header.css file and change the following highlighted lines:
      .App-logo {
    -   height: 40vmin;
    +   height: 60px;
        pointer-events: none;
      }
      // ... 
      .App-header {
        background-color: #282c34;
    -   min-height: 100vh;
    +   min-height: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        font-size: calc(10px + 2vmin);
        color: white;
      }
  3. There should be enough free space on our page to display the Profile component, so we can open the scr/pages/Profile.js file once more and display the avatar_url, html_url, repos_url, name, company, location, email, and bio fields that were returned by the GitHub API:
      // ...
      return (
        <div>
          <h2>About me</h2> 
          {loading ? (
            <span>Loading...</span>
          ) : (
            <ul>
    +         <li><span>avatar_url: </span>
                {profile.avatar_url}</li>
    +         <li><span>html_url: </span> 
                {profile.html_url}</li>
    +         <li><span>repos_url: </span> 
                {profile.repos_url}</li>
    +         <li><span>name: </span> {profile.name}</li>
    +         <li><span>company: </span>
                {profile.company}</li>
    +         <li><span>location: </span>
                {profile.location}</li>
    +         <li><span>email: </span>
                {profile.email}</li>
    +         <li><span>bio: </span> {profile.bio}</li>
            </ul>
          )}
        </div>
      );
    }
    export default Profile;

Once we've saved this file and visited our project in the browser, we will see a bullet list of the GitHub information being displayed.

Since this doesn't look very pretty and the header doesn't match the content of the page, let's make some changes to the styling files for these two components:

  1. Change the code for the Header component so that it will display a different title for the page. Also, the Link component can be deleted from here, as we'll be using it in a Profile component later on:
      import './Header.css';
    - import Link from './Link';
      function Header({ logo }) {
        return (
          <header className='App-header'>
            <img src={logo} className='App-logo'
             alt='logo' />
    -       <p>
    -         Edit <code>src/App.js</code> and save to
                reload.
    -       </p>
    -       <Link url='https://reactjs.org' 
              title='Learn React' />
    +       <h1>My Portfolio</h1>
          </header>
        );
      }
      export default Header;
  2. Before changing the styling of the Profile component, we first need to create a CSS file that will hold the styling rules for the component. To do so, create the Profile.css file in the pages directory and add the following content:
    .Profile-container {
      width: 50%;
      margin: 10px auto;
    }
    .Profile-avatar {
      width: 150px;
    }
    .Profile-container > ul {
      list-style: none;
      padding: 0;
      text-align: left;
    }
    .Profile-container > ul > li {
      display: flex;
      justify-content: space-between;
    }
    .Profile-container > ul > li > span {
      font-weight: 600;
    }
  3. In src/pages/Profile.js, we need to import this file to apply the styling. Remember the Link component we created previously? We also import this file, as it will be used to create a link to our profile and a list of repositories on the GitHub website:
      import { useState, useEffect } from 'react';
    + import Link from '../components/Link';
    + import './Profile.css';
      function Profile({ userName }) {
        
        // ..
  4. In the return statement, we'll add the classNames function that we defined in the styling and separate the avatar image from the bullet list. By doing that, we also need to wrap the bullet list with an extra div:
      // ...
      return (
    -   <div>
    +   <div className='Profile-container'>    
          <h2>About me</h2>
          {loading ? (
            <span>Loading...</span>
          ) : (
    +       <div>
    +         <img
    +           className='Profile-avatar'
    +           src={profile.avatar_url}
    +           alt={profile.name}
    +         />
              <ul>
    -           <li><span>avatar_url: </span>
                  {profile.avatar_url}</li>
    -           <li><span>html_url: </span>
                  {profile.html_url}</li>
    -           <li><span>repos_url: </span>
                  {profile.repos_url}</li>
    +           <li>
    +             <span>html_url: </span>
    +             <Link url={profile.html_url}
                   title={profile.html_url} />
    +           </li>
    +           <li>
    +             <span>repos_url: </span>
    +             <Link url={profile.repos_url} 
                    title={profile.repos_url} />
    +           </li>
                <li><span>name: </span>
                  {profile.name}</li>
                <li><span>company: </span>
                  {profile.company}</li>
                <li><span>location: </span>
                  {profile.location}</li>
                <li><span>email: </span>
                  {profile.email}</li>
                <li><span>bio: </span> {profile.bio}</li>
             </ul>
    +      </div>
         );
       }
       // ..

Finally, we can see that the application is starting to look like a portfolio page loading your GitHub information, including your avatar and a list of the public information. This results in an application that looks similar to what's shown in the following screenshot:

Figure 2.3 – Our styled portfolio application

Figure 2.3 – Our styled portfolio application

If we take a look at the code in the Profile component, we'll see that there is a lot of duplicate code, so we need to transform the list that's displaying our public information into a separate component. Let's get started:

  1. Create a new file called List.js inside the components directory, which will take a prop called items:
    function List({ items }) {
      return (
        <ul></ul>
      );
    }
    export default List;
  2. In the Profile component, we can import this new List component. A new variable called items should be created, which is an array containing all the items we want to display inside this list:
      import { useState, useEffect } from 'react';
    + import List from '../components/List';
      import Link from '../components/Link';
      import './Profile.css';
      function Profile({ userName }) {
        // …
    +   const items = [
    +     {
    +       field: 'html_url',
    +       value: <Link url={profile.html_url} 
              title={profile.html_url} />,
    +     },
    +     {
    +       field: 'repos_url',
    +       value: <Link url={profile.repos_url}
              title={profile.repos_url} />,
    +     },
    +     { field: 'name', value: profile.name },
    +     { field: 'company', value: profile.company },
    +     { field: 'location', value: profile.location },
    +     { field: 'email', value: profile.email },
    +     { field: 'bio', value: profile.bio },
    +   ];
        // ...
  3. This will be sent as a prop to the List component, so these items can be rendered from that component instead. This means that you can remove the ul element and all the li elements inside:
        // ...  
        return (
          <div className='Profile-container'>
            <h2>About me</h2>
            {loading ? (
              <span>Loading...</span>
            ) : (
              <div>
                <img
                  className='Profile-avatar'
                  src={profile.avatar_url}
                  alt={profile.name}
                />
    -           <ul>
    -             // ...
    -           </ul>
    +           <List items={items} />
              </div>
            )}
          </div>
        );
      }
    export default Profile;

You can see that for the list item with the html_url and repos_url fields, we'll be sending the Link component as a value instead of the value that was returned from the GitHub API. In React, you can also send complete components as a prop to a different component, as props can be anything.

  1. In the List component, we can now map over the items prop and return the list items:
      // ...
      function List({ items }) {
        return (
          <ul>
    +       {items.map((item) => (
    +         <li key={item.field}>
    +           <span>{item.field}: </span>
    +           {item.value}
    +         </li>
    +       ))}
          </ul>
        );
      }
      export default List;

The styling is inherited from the Profile component, as the List component is a child component. To structure your application better, you can move the styling for the list of information to a separate List.css file and import it inside the List component.

Assuming we executed the preceding steps correctly, your application shouldn't have changed aesthetically. However, if we take a look at the React Developer Tools, we will see that some changes have been made to the component tree.

In the next section, we'll add routing with react-router and display repositories that are linked to our GitHub account.

Routing with react-router

react-router v6 is the most popular library in React for routing, and it supports lots of features to help you get the most out of it. With this library, you can add declarative routing to a React application, just by adding components. These components can be divided into three types: router components, route matching components, and navigation components.

Setting up routing with react-router consists of multiple steps:

  1. To use these components, you need to install the react-router web package, called react-router-dom, by executing the following:
    npm install react-router-dom
  2. After installing react-router-dom, the next step is to import the routing and route matching components from this package into the container component of your application. In this case, that is the App component, which is inside the src directory:
      import React from 'react';
    + import { BrowserRouter, Routes, Route } 
        from 'react-router-dom';
      import logo from './assets/logo.svg';
      import './App.css';
      import Header from './components/Header';
      import Profile from './pages/Profile';
      function App() {
        // …
  3. The actual routes must be added to the return statement of this component, where all of the route matching components (Route) must be wrapped in a routing component, called Router. When your URL matches a route defined in any of the iterations of Route, this component will render the React component that passed as a child:
      // ...
      function App() {
        return (
          <div className='App'>
    +       <BrowserRouter>
            <Header logo={logo} />
    -         <Profile userName='octocat' />
    +         <Routes>
    +           <Route 
    +             path='/'
    +             element={<Profile userName='octocat' />}
    +           />
    +         </Routes>
    +       </BrowserRouter>
          </div>
        );
      }
      export default App;

If you now visit the project in the browser again at http://localhost:3000, the Profile component will be rendered.

Besides our GitHub profile, we also want to showcase the projects we've been working on. Let's add a new route to the application, which will render all the repositories of our GitHub account:

  1. This new component will use the endpoint to get all your repositories, which you can try out by executing the following command (replace username at the end of the bold section of code with your own username):
    curl 'https://api.github.com/users/username/repos'

The output of calling this endpoint will look something like this:

[
  {
    "id": 132935648,
    "node_id": "MDEwOlJlcG9zaXRvcnkxMzI5MzU2NDg=",
    "name": "boysenberry-repo-1",
    "full_name": "octocat/boysenberry-repo-1",
    "private": false,
    "html_url":
      "https://github.com/octocat/boysenberry-repo-1",
    "description": "Testing",
    "fork": true,
    "created_at": "2018-05-10T17:51:29Z",
    "updated_at": "2021-01-13T19:56:01Z",
    "pushed_at": "2018-05-10T17:52:17Z",
    "stargazers_count": 9,
    "watchers_count": 9,
    "forks": 6,
    "open_issues": 0,
    "watchers": 9,
    "default_branch": "master"
  },
  // ...
]

As you can see from the preceding sample response, the repositories data is an array with objects. We'll be using the preceding highlighted fields to display our repositories on the /projects route.

  1. First, we need to create a new component called Projects in the pages directory. This component will have almost the same logic for state management and data fetching as the Profile component, but it will call a different endpoint to get the repositories instead:
    import { useState, useEffect } from 'react';
    import Link from '../components/Link';
    import List from '../components/List;
    function Projects({ userName }) {
      const [loading, setLoading] = useState(true);
      const [projects, setProjects] = useState({});
      useEffect(() => {
        async function fetchData() {
          const data = await fetch(
           'https://api.github.com/users/${
            userName}/repos',
          );
          const result = await data.json();
          if (result) {
            setProjects(result);
            setLoading(false);
          }
        }
        fetchData();
      }, [userName]);
      
      // ...
  2. After putting the information from the endpoint to the local state variable projects, we'll use the same List component to render the information about the repositories:
      // ...
      return (
        <div className='Projects-container'>
          <h2>Projects</h2>
          {loading ? (
            <span>Loading...</span>
          ) : (
            <div>
              <List items={projects.map((project) => ({
                field: project.name,
                value: <Link url={project.html_url}
                title={project.html_url} />,
      }))} />
            </div>
          )}
        </div>
      );
    }
    export default Projects;
  3. To have this component render when we visit the /profile route, we need to add it to the App component using a Route component:
      import React from 'react';
      import { BrowserRouter, Routes, Route } 
        from 'react-router-dom';
      import logo from './assets/logo.svg';
      import './App.css';
      import Header from './components/Header';
      import Profile from './pages/Profile';
    + import Projects from './pages/Projects';
      function App() {
        return (
          <div className='App'>
            <Header logo={logo} />
            <BrowserRouter>
              <Routes>
                <Route path='/' element={  <Profile
                   userName='octocat' />} />
    +           <Route path='/projects' element=
                     {<Projects userName='octocat' />} />
                </Routes>
              </BrowserRouter>
            // ...

The Profile component will now only be rendered if you visit the / route, and the Projects component when you visit the /projects route. No component will be rendered besides the Header component if you visit any other route.

Note

You can set a component that will be displayed when no route can be matched by passing * as a path to the Route component.

Although we have two routes set up, the only way to visit these routes is by changing the URL in the browser. With react-router, we can also create dynamic links to visit these routes from any component. In our Header component, we can add a navigation bar that renders links to these routes:

  import './Header.css';
+ import { Link as RouterLink } from 'react-router-dom';
  function Header({ logo }) {
    return (
      <header className='App-header'>
        <img src={logo} className='App-logo' alt='logo' />
        <h1>My Portfolio</h1>
+       <nav>
+         <RouterLink to='/' className='App-link'>
+           About me
+         </RouterLink>
+         <RouterLink to='/projects' className='App-link'>
+           Projects
+         </RouterLink>
+       </nav>
      </header>
    );
  }
  export default Header;

As we already have a Link component defined ourselves, we're importing the Link component from react-router-dom as RouterLink. This will prevent confusion if you make any changes later on, or when you're using an autocomplete feature in your IDE.

Finally, we can add some styling to Header.css so that the links to our routes are displayed nicely:

  .App-header {
    background-color: #282c34;
    min-height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
  }
+ .App-header > nav {
+   margin-bottom: 10px;
+ }
+ .App-header > nav > .App-link {
+   margin-right: 10px;
+ }

If you now visit the application in the browser at http://localhost:3000/projects, it should look something like the following screenshot. Clicking on the links in the header will navigate you between the two different routes:

Figure 2.4 – The Projects route in our application

Figure 2.4 – The Projects route in our application

With these routes in place, even more routes can be added to the router component. A logical one is having a route for individual projects, which has an extra parameter that specifies which projects should be displayed. Therefore, we have a new component called the ProjectDetailpages directory, which contains the logic for fetching an individual repository from GitHub API. This component is rendered when the path matches /projects/:name, where name stands for the name of the repository that is clicked on on the projects page:

  1. This route uses a new component in a file called ProjectDetail.js, which is similar to the Projects component. You can also create this file in the pages directory, except that it will be fetching data from the https://api.github.com/repos/userName/repo endpoint, where userName and repo should be replaced with your own username and the name of the repository that you want to display:
    import { useState, useEffect } from 'react';
    Import { useParams } from 'react-router-dom';
    function Project({ userName }) {
      const [loading, setLoading] = useState(false);
      const [project, setProject] = useState([]);
      const { name } = useParams();
      useEffect(() => {
        async function fetchData() {
          const data = await fetch(
            'https://api.github.com/repos/${
             userName}/${name}',
          );
          const result = await data.json();
          if (result) {
            setProject(result);
            setLoading(false);
          }
        }
        if (userName && name) {
          fetchData();
        }
      }, [userName, name]);
      // ...

In the preceding section, you can see how the data is retrieved from the GitHub API, using both your username and the name of the repository. The name of the repository comes from the useParams Hook from react-router-dom, which gets the name variable from the URL for you.

  1. With the repository data retrieved from GitHub, you can create the items variable that is used to render information about this project using the List component that we also used in the previous routes. The fields that are added to items are coming from GitHub and can also be seen in the response of the https://api.github.com/users/username/repos endpoint that we inspected previously. Also, the name of the repository is listed previously:
     // ...
      return (
        <div className='Project-container'>
          <h2>Project: {project.name}</h2>
          {loading ? (
            <span>Loading...</span>
          ) : (
            <div></div>
          )}
        </div>
      );
    }
    export default Project;
  2. To render this component on the /projects/:name route, we need to add this component within the Router component inside App.js:
      // ...
    + import ProjectDetail from './pages/ProjectDetail';
      function App() {
        return (
          <div className='App'>
            <BrowserRouter>
              <Header logo={logo} />
              <Routes>
                <Route exact path='/' element=
                  {<Profile userName='octocat' />} />
                <Route path='/projects' elements=
                  {<Projects userName='octocat' />} />
    +           <Route path='/projects/:name' element=
                  {<ProjectDetail userName='octocat' />} 
                />
            </Routes>
          </BrowserRouter>
        );
      }
  3. You can already navigate to this route by changing the URL in the browser, but you also want to add a link to this page in the Projects component. Therefore, you need to make changes that will import RouterLink from react-router-dom and use it instead of your own Link component:
      import { useState, useEffect } from 'react';
    + import { Link as RouterLink } from 
        'react-router-dom'
      import List from '../components/List';
    - import Link from '../components/Link';
      // ...
      return (
        <div className='Projects-container'>
          <h2>Projects</h2>
          {loading ? (
            <span>Loading...</span>
          ) : (
            <div>
              <List items={projects.map((project) => ({
                field: project.name,
    -           value: <Link url={project.html_url}
                title={project.html_url} />,
                  }))items} />
    +           value: <RouterLink url={project.html_url}
                title={project.html_url} />,
                  }))items} />
            </div>
          )}
        </div>
      );
    }
    export default Projects;

If you now visit the http://localhost:3000/projects page in the browser, you can click on the projects and move on to a new page that shows all the information for a specific project.

With these last changes, you've created a portfolio application that uses react-router for dynamic routing.

 

Summary

In this chapter, you used Create React App to create your starter project for a React application, which comes with an initial configuration for libraries such as Babel and Webpack. By doing this, you don't have to configure these libraries yourself and worry about how your React code will run in the browser. We've looked into building reusable components in this chapter and learned how to add dynamic routing with react-router. With this library, you can create applications that have tons of routes, and you're able to use changes in the URL to change what is displayed inside your application.

The upcoming chapters will all feature projects that are created with Create React App or other zero-config libraries, meaning that these projects don't require you to make changes to Webpack or Babel.

In the next chapter, we will build upon this chapter by creating a dynamic project management board with React that uses styled-components for styling and reuses logic with custom Hooks.

 
About the Author
  • Roy Derks

    Roy Derks is a serial start-up CTO, international speaker, and author from the Netherlands. He has been working with React, React Native, and GraphQL since 2016. You might know him from the book “React Projects – Second Edition”, which was released by Packt earlier this year. Over the last few years, he has inspired tens of thousands of developers worldwide through his talks, books, workshops, and courses.

    Browse publications by this author
React Projects - Second Edition
Unlock this book and the full library FREE for 7 days
Start now