One place for hosting & domains

      Handle

      How To Handle Images with GraphQL and the Gatsby Image API


      The author selected /dev/color to receive a donation as part of the Write for DOnations program.

      Introduction

      Handling images plays a pivotal role in building websites, but also can be challenging to deal with. Unoptimized images slow down websites, and many images that might look appropriate on a desktop are hard to scale down to a mobile device. Visually manipulating an image can also be tedious and difficult to maintain.

      All of these problems in isolation are not a big issue. The main problem is when you have to keep track of all of these rules and image-scaling techniques. When it comes to Gatsby.js projects, this is where the Gatsby Image API comes in handy. By using GraphQL queries, you can use the Gatsby Image API to take care of image compression, make an image responsive, and even handle basic image styling.

      In this tutorial, you are going to compress, transform, and style images using the Gatsby Image API and GraphQL queries.

      Prerequisites

      Step 1 — Setting Up a New Gatsby Project

      In this first step, you are going to set up a new Gatsby project and familiarize yourself with the key image plugins that you’ll use throughout this tutorial. You will also download and set up an image to optimize throughout the tutorial.

      First, use the CLI tool to start a new project named gatsby-image-project:

      • gatsby new gatsby-image-project https://github.com/gatsbyjs/gatsby-starter-default

      This creates a new website from the starter template in the [gatsby-starter-default](https://github.com/gatsbyjs/gatsby-starter-default) GitHub repository from Gatsby.

      Once the project is created, move into the new project directory:

      Next, open up the index.js file in a text editor of your choice:

      Delete all of the code between the layout wrapper component so that your file is the same as the following:

      gatsby-image-project/src/pages/index.js

      import React from "react"
      import { Link } from "gatsby"
      
      import Layout from "../components/layout"
      import Image from "../components/image"
      import SEO from "../components/seo"
      
      const IndexPage = () => (
        <Layout>
        </Layout>
      )
      
      export default IndexPage
      

      Next, replace the deleted code with the following highlighted JSX, which adds some HTML elements to the website:

      gatsby-image-project/src/pages/index.js

      import React from "react"
      import { Link } from "gatsby"
      
      import Layout from "../components/layout"
      import Image from "../components/image"
      import SEO from "../components/seo"
      
      const IndexPage = () => (
          <Layout>
            <div className="layout">
              <Image className="left-image"/>
              <h2>Hello</h2>
              <p>Welcome to my humble site</p>
              <p>All of our shirts are on sale!</p>
              <button className="shop-button">Shop</button>
            </div>
          </Layout>
      )
      
      export default IndexPage
      

      The Gatsby Image API will provide your new test image to the Image element in a later step. With this, you now have HTML to experiment with.

      Later in the tutorial, you’ll revisit the index.js page. For now, save and exit the file.

      The next file to open is gatsby-config.js. In this file, you will find the plugins responsible for processing images.

      Open up the file with the following:

      Once you have opened the gatsby-config file, locate the gatsby-plugin-sharp, gatsby-transformer-sharp, and gatsby-source-filesystem plugins:

      gatsby-image-project/gatsby-config.js

      module.exports = {
        siteMetadata: {
          title: `Gatsby Default Starter`,
          description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
          author: `@gatsbyjs`,
        },
        plugins: [
          `gatsby-plugin-react-helmet`,
          {
            resolve: `gatsby-source-filesystem`,
            options: {
              name: `images`,
              path: `${__dirname}/src/images`,
            },
          },
          `gatsby-transformer-sharp`,
          `gatsby-plugin-sharp`,
          {
            resolve: `gatsby-plugin-manifest`,
            options: {
              name: `gatsby-starter-default`,
              short_name: `starter`,
              start_url: `/`,
              background_color: `#663399`,
              theme_color: `#663399`,
              display: `minimal-ui`,
              icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
            },
          },
          // this (optional) plugin enables Progressive Web App + Offline functionality
          // To learn more, visit: https://gatsby.dev/offline
          // `gatsby-plugin-offline`,
        ],
      }
      

      These plugins are as follows:

      • gatsby-plugin-sharp: Sharp is an image optimization library that Gatsby uses to process images. The gatsby-plugin-sharp provides a bridge between Sharp and Gatsby.

      • gatsby-transformer-sharp: This plugin performs image transformations, such as resizing, compressing, and changing background color.

      • gatsby-source-filesystem: This plugin allows you to source data from your filesystem into your application. In this case, it enables GraphQL to query images.

      Now that you have an idea of which plugins are used to process images, close the file.

      Next, add an image to your application to optimize, edit, and style later. In this tutorial, you are going to download an image from the Unsplash stock image website. Navigate to this picture of clothes on Unsplash in a browser and download the angela-bailey-jlo7Bf4tUoY-unsplash.jpg image into the /images folder of your Gatsby project. The image must be located in the right directory in order to query the image with GraphQL and transform it using Gatsby’s Image API.

      Alternatively, you can download the image from the command line. First, move to the images directory:

      Next, execute the following command:

      • curl -sL https://images.unsplash.com/photo-1556905055-8f358a7a47b2 -o angela-bailey-jlo7Bf4tUoY-unsplash.jpg

      This will use curl to download the image and output the file as angela-bailey-jlo7Bf4tUoY-unsplash.jpg.

      In this section, you set up your Gatsby project to use Gatsby’s Image API. You explored the Gatsby configuration file to find gatsby-plugin-sharp, gatsby-transform-sharp, and gatsby-source-filesystem, which work together to optimize images. In the next step, you will test out querying and optimizing your image using GraphiQL, the GraphQL integrated development environment (IDE).

      Step 2 — Querying Images with GraphQL

      You are now going to query your new image using GraphQL. GraphQL is a query language for obtaining information from an API. It is also the data layer of Gatsby.

      First, return to the root of your Gatsby project, then start the development server:

      After your site finishes building, you will receive the following output:

      Output

      ... success open and validate gatsby-configs - 0.081s success load plugins - 4.537s success onPreInit - 0.070s success initialize cache - 0.034s success copy gatsby files - 0.320s success onPreBootstrap - 0.177s success createSchemaCustomization - 0.050s success Checking for changed pages - 0.003s success source and transform nodes - 0.264s success building schema - 0.599s info Total nodes: 35, SitePage nodes: 1 (use --verbose for breakdown) success createPages - 0.057s success Checking for changed pages - 0.005s success createPagesStatefully - 0.188s success update schema - 0.046s success write out redirect data - 0.006s success Build manifest and related icons - 0.456s success onPostBootstrap - 0.465s info bootstrap finished - 19.515s success onPreExtractQueries - 0.003s success extract queries from components - 0.932s success write out requires - 0.029s success run page queries - 0.039s - 1/1 25.97/s ⠀ You can now view gatsby-starter-default in the browser. ⠀ http://localhost:8000/ ⠀ View GraphiQL, an in-browser IDE, to explore your site's data and schema ⠀ http://localhost:8000/___graphql ⠀ Note that the development build is not optimized. To create a production build, use gatsby build ⠀ warn ESLintError: /your_filepath/gatsby-image-project/src/pages/index.js 2:10 warning 'Link' is defined but never used no-unused-vars 6:8 warning 'SEO' is defined but never used no-unused-vars ✖ 2 problems (0 errors, 2 warnings) success Building development bundle - 10.814s

      This output contains two links. The first link, https://localhost:8000/, is where you can find your local development site. The second link, http://localhost:8000/___graphql, is the location of GraphiQL. GraphiQL is an integrated development editor (IDE) that allows you to make queries in the browser. This is a useful tool that helps you experiment and make data queries before you add them to your codebase. GraphiQL only works when you are running the development server.

      With the help of GraphiQL, you can try out queries to retrieve your newly downloaded image. Open your browser and enter the GraphiQL URL http://localhost:8000/___graphql into the address bar. The browser will display the GraphiQL interface:

      Screenshot of the GraphQL IDE

      GraphiQL is split into three sections. To the far left is the Explorer, where you can find the fields that you are able to access via GraphQL. In the middle of the IDE is the sandbox where you make queries. Finally, to the far right you can find GraphQL’s documentation.

      Your first goal is to query the angela-bailey-jlo7Bf4tUoY-unsplash.jpg image. Since the image is located in the local filesystem, you choose file in the Explorer box. This will show a dropdown menu of subdirectories. You will search for the image file by relative path, so select on relativePath. relativePath reveals another set of subfolders. You will enter the exact path of the image, so choose the eq for “equals”. Inside the quotes enter the path of the image angela-bailey-jlo7Bf4tUoY-unsplash.jpg.

      GraphQL IDE showing the query to locate the image in the filesystem

      Now you are set to try out your first image manipulation. Your original image is 1920 by 1280 pixels. That is too big for your landing page, and it would help to make the image responsive. Normally, you would have to hard code the width into CSS and add media queries to make the image responsive. Gatsby’s Image API does all of that work for you, without you needing to write extra CSS.

      Select childImageSharp in the Explorer box, which transforms the image under the hood. Make sure to choose this from the top-level menu, not from under file. Choose fluid from the next dropdown. A fluid image stretches to fill its container. In the dropdown options for fluid, check maxWidth and enter the 750.

      The blue values just below the purple querying parameters are the different values you can return. Choose src and srcSet to return the location of the original and transformed image. Then click the play button to view the results:

      childImageSharp parameters

      After selecting the parameters, GraphiQL builds the following query:

      query MyQuery {
        file(relativePath: {eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg"}) {
          childImageSharp {
            fluid(maxWidth: 750) {
              src
              srcSet
            }
          }
        }
      }
      

      In the box on the right of the GraphiQL interface, you will find the return values. This will show:

      • src: Location of the image after processing.

      • srcSet: Same image set to a different size. This feature comes in handy if you want your images to be responsive.

      You do not have to choose fluid in your query; you also have the option to choose fix. A fixed image creates responsive images 1x, 1.5x, and 2x pixel densities using the <picture> element. The following is an example of a fixed query:

      query MyQuery {
        file(relativePath: {eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg"}) {
          childImageSharp {
            fixed(cropFocus: CENTER) {
              src
              srcSet
            }
          }
        }
      }
      

      This query will return the following:

      {
        "data": {
          "file": {
            "childImageSharp": {
              "fixed": {
                "src": "/static/8e3a47b77ddf6636755d7be661d7b019/0ad16/angela-bailey-jlo7Bf4tUoY-unsplash.jpg",
                "srcSet": "/static/8e3a47b77ddf6636755d7be661d7b019/0ad16/angela-bailey-jlo7Bf4tUoY-unsplash.jpg 1x,n/static/8e3a47b77ddf6636755d7be661d7b019/44157/angela-bailey-jlo7Bf4tUoY-unsplash.jpg 1.5x,n/static/8e3a47b77ddf6636755d7be661d7b019/7fddd/angela-bailey-jlo7Bf4tUoY-unsplash.jpg 2x"
              }
            }
          }
        },
        "extensions": {}
      }
      

      In this query you have a fixed version of the image, with the crop focus set to center. Your return value is the location of the image and a set of different image sizes (1x, 1.5x, 2x respectively).

      Now that you have tested out the GraphQL query using GraphiQL and the childImageSharp node, you will next add the queried image to a template and further optimize it using Gatsby’s Image API.

      Step 3 — Optimizing Your Image’s Performance for the Web

      In this section you are going to transfer your GraphQL image to the index.js page of your project and perform more image optimizations.

      From your terminal in the root of your Gatsby project, open the image component in your favorite text editor:

      • nano src/components/image.js

      Now, use the query that you tried out in the GraphiQL interface. Delete "gatsby-astronaut.png" and replace it with "angela-bailey-jlo7Bf4tUoY-unsplash.jpg". Also replace maxWidth: 300 with maxWidth: 750:

      gatsby-image-project/src/components/image.js

      import React from "react"
      import { useStaticQuery, graphql } from "gatsby"
      import Img from "gatsby-image"
      
      /*
       * This component is built using `gatsby-image` to automatically serve optimized
       * images with lazy loading and reduced file sizes. The image is loaded using a
       * `useStaticQuery`, which allows us to load the image from directly within this
       * component, rather than having to pass the image data down from pages.
       *
       * For more information, see the docs:
       * - `gatsby-image`: https://gatsby.dev/gatsby-image
       * - `useStaticQuery`: https://www.gatsbyjs.com/docs/use-static-query/
       */
      
      const Image = () => {
        const data = useStaticQuery(graphql`
          query {
            placeholderImage: file(relativePath: { eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg" }) {
              childImageSharp {
                fluid(maxWidth: 750) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        `)
      
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
      }
      
      export default Image
      

      ...GatsbyImageSharpFluid is a GraphQL fragment. This syntax allows you to obtain all of the different return values for childImageSharp and fluid. You will use this as the return value instead of src and srcSet.

      GraphiQL explorer list of return values for `childImageSharp` and `fluid`

      In the image.js file, after the GraphQL query there is an if statement:

      gatsby-image-project/src/components/image.js

      ...
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
      }
      
      export default Image
      

      placeholderimage is the alias that is given in the query and returned in <Img fluid={data.placeholderImage.childImageSharp.fluid} />. In GraphQL you are able to name your queries. In the conditional statement, if you don’t have an image, then the words Picture not found will appear on the landing page.

      Save and close this file.

      Once you build your Gatsby site, the image will be processed and optimized by the Gatsby Image API. This will decrease the size of the image file to decrease loading time for your site. To test this out, navigate to the images directory to find the original file size:

      Now list out the files with the following command:

      The -sh flag will show you the memory size of the files in a human-readable format. You will receive the following output:

      Output

      total 5.0M 4.8M angela-bailey-jlo7Bf4tUoY-unsplash.jpg 164K gatsby-astronaut.png 24K gatsby-icon.png

      Without any optimization, the image is 4.8M. Now you will look at how big the image is after you’ve used Gatsby’s Image API.

      Navigate to the root of your project and start the development server:

      Once the development server has started, place the local address into the browser. Once the site has loaded, right-click the image and select Inspect.

      Image with Inspect dropdown menu

      Now navigate to the Network tab of your browser’s developer tools. This tutorial will use Google Chrome DevTools:

      Local image size

      Your image went from 4.8 MB to 68.4 kB. That is significantly smaller than if you hadn’t used the Gatsby Image API.

      Note: If the HTTP status for the image request is 304, the image may be significantly smaller due to caching. To get a more reliable view of the image size, clear the cache and refresh the page.

      Keep developer tools open and head over to the Elements tab. Hover over the image. You will find the HTML element <picture>...</picture> and its child <source>...</source>:

      Rendered HTML of the Gatsby site

      In the source tag, you can find the srcSet attribute (the same one you queried for in GraphQL). You will also find that the different heights and widths of the image were automatically generated. These different images ensure that angela-bailey-jlo7Bf4tUoY-unsplash.jpg is fluid, without needing to change CSS.

      Note: While hovering over the image you might have noticed that the image is not keyboard-focusable, which could be an accessibility problem. If you want to learn more about accessibility, you can check out the Ally Project checklist.

      In this section you used the GraphQL Image query in your Gatsby template. You also optimized angela-bailey-jlo7Bf4tUoY-unsplash.jpg without having to write extra CSS.

      In the next section, you will visually transform the image by using childSharpImage.

      Step 4 — Styling Your Image with childSharpImage

      In this section, you will transform the look of angela-bailey-jlo7Bf4tUoY-unsplash.jpg with the help of childSharpImage. To test this, you will change your image to grayscale.

      Open image.js in a text editor:

      • nano src/components/image.js

      Go into your placeholderImage GraphQL query and inside of fluid set grayscale to true:

      gatsby-image-project/src/components/image.js

      import React from "react"
      import { useStaticQuery, graphql } from "gatsby"
      import Img from "gatsby-image"
      
      /*
       * This component is built using `gatsby-image` to automatically serve optimized
       * images with lazy loading and reduced file sizes. The image is loaded using a
       * `useStaticQuery`, which allows us to load the image from directly within this
       * component, rather than having to pass the image data down from pages.
       *
       * For more information, see the docs:
       * - `gatsby-image`: https://gatsby.dev/gatsby-image
       * - `useStaticQuery`: https://www.gatsbyjs.com/docs/use-static-query/
       */
      
      const Image = () => {
        const data = useStaticQuery(graphql`
          query {
            placeholderImage: file(relativePath: { eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg" }) {
              childImageSharp {
                fluid(
                  maxWidth: 750
                  grayscale: true
                  ) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        `)
      
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        return <Img fluid={data.placeholderImage.childImageSharp.fluid} tabIndex='0' />
      }
      
      export default Image
      

      Save and close the file.

      Go back to your terminal and restart the server. You will find your image changed using childImageSharp, without writing and maintaining unnecessary CSS:

      Grayscale image

      Grayscale is just one of the many ways you can process your image using childImageSharp. Go to Gatsby’s documentation if you are curious about the other childImageSharp props.

      In this section you implemented grayscale by using childImageSharp. With this knowledge, you can perform many more image manipulations by leveraging the Gatsby Image API.

      Conclusion

      In this tutorial, you set your Gatsby project up to use the Gatsby Image API, query your Image using GraphQL, optimize your image’s performance, and style your image using childImageSharp. If you would like to learn more about Gatsby, check out the official Gatsby documentation.



      Source link

      How To Handle Routing in React Apps with React Router


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      In React, routers help create and navigate between the different URLs that make up your web application. They allow your user to move between the components of your app while preserving user state, and can provide unique URLs for these components to make them more shareable. With routers, you can improve your app’s user experience by simplifying site navigation.

      React Router is one of the most popular routing frameworks for React. The library is designed with intuitive components to let you build a declarative routing system for your application. This means that you can declare exactly which of your components has a certain route. With declarative routing, you can create intuitive routes that are human-readable, making it easier to manage your application architecture.

      In this tutorial, you’ll install and configure React Router, build a set of routes, and connect to them using the <Link> component. You’ll also build dynamic routes that collect data from a URL that you can access in your component. Finally, you’ll use Hooks to access data and other routing information and create nested routes that live inside components that are rendered by parent routes.

      By the end of this tutorial, you’ll be able to add routes to any React project and read information from your routes so that you can create flexible components that respond to URL data.

      Prerequisites

      Step 1 — Installing React Router

      In this step, you’ll install React Router into your base project. In this project, you are going to make a small website about marine mammals. Each mammal will need a separate component that you’ll render with the router. After installing the library, you’ll create a series of components for each mammal. By the end of this step, you’ll have a foundation for rendering different mammals based on route.

      To start, install the React Router package. There are two different versions: a web version and a native version for use with React Native. You will install the web version.

      In your terminal, use npm to install the package:

      • npm install react-router-dom

      The package will install and you’ll receive a message such as this one when the installation is complete. Your message may vary slightly:

      Output

      ... + react-router-dom@5.2.0 added 11 packages from 6 contributors and audited 1981 packages in 24.897s 114 packages are looking for funding run `npm fund` for details found 0 vulnerabilities

      You now have the package installed. For the remainder of this step, you’ll create a series of components that will each have a unique route.

      To start, make a directory for three different mammals: manatees, narwhals, and whales. Run the following commands:

      • mkdir src/components/Manatee
      • mkdir src/components/Narwhal
      • mkdir src/components/Whale

      Next, create a component for each animal. Add an <h2> tag for each mammal. In a full application, the child components can be as complex as you want. They can even import and render their own child components. For this tutorial, you’ll render only the <h2> tag.

      Begin with the manatee component. Open Manatee.js in your text editor:

      • nano src/components/Manatee/Manatee.js

      Then add the basic component:

      router-tutorial/src/components/Manatee/Manatee.js

      import React from 'react';
      
      export default function Manatee() {
        return <h2>Manatee</h2>;
      }
      

      Save and close the file.

      Next, create a component for the narwhal:

      • nano src/components/Narwhal/Narwhal.js

      Add the same basic component, changing the <h2> to Narwhal:

      router-tutorial/src/components/Narwhal/Narwhal.js

      import React from 'react';
      
      export default function Narwhal() {
        return <h2>Narwhal</h2>;
      }
      

      Save and close the file.

      Finally, create a file for Whale:

      • nano src/components/Whale/Whale.js

      Add the same basic component, changing the <h2> to Whale:

      router-tutorial/src/components/Whale/Whale.js

      import React from 'react';
      
      export default function Whale() {
        return <h2>Whale</h2>;
      }
      

      Save and close the file. In the next step, you’ll start connecting routes; for now, render the basic component in your application.

      Open App.js:

      • nano src/components/App/App.js

      Add an <h1> tag with the name of the website (Marine Mammals) inside of a <div> with a className of wrapper. This will serve as a template. The wrapper and <h1> tag will render on every page. In full applications, you might add a navigation bar or a header component that you’d want on every page.

      Add the following highlighted lines to the file:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import './App.css';
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
          </div>
        );
      }
      
      export default App;
      

      Next, import Manatee and render inside the <div>. This will serve as a placeholder until you add more routes:

      router-tutorial/src/components/App/App.js

      
      import React from 'react';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <Manatee />
          </div>
        );
      }
      
      export default App;
      

      Save and close the file.

      Now that you have all of the components, add some padding to give the application a little space.

      Open App.css:

      • nano src/components/App/App.css

      Then replace the contents with the following code that adds a padding of 20px to the .wrapper class:

      router-tutorial/src/components/App/App.css

      .wrapper {
          padding: 20px;
      }
      

      Save and close the file. When you do, the browser will refresh to show your basic component:

      Marine Mammals

      Now you have a basic root component that you will use to display other components. If you didn’t have a router, you could conditionally display components using the useState Hook. But this wouldn’t offer a great experience for your users. Anytime a user refreshes the page, the user’s selection would disappear. Further, they wouldn’t be able to bookmark or share specific states of the application. A router will solve all these problems. The router will preserve the user state and will give the user a clear URL that they can save or send to others.

      In this step, you installed React Router and created basic components. The components are going to be individual pages that you’ll display by route. In the next step, you’ll add routes and use the <Link> component to create performant hyperlinks.

      Step 2 — Adding Routes

      In this step, you’ll create a base router with individual routes for each page. You’ll order your routes to ensure that components are rendered correctly and you’ll use the <Link> component to add hyperlinks to your project that won’t trigger a page refresh.

      By the end of this step, you’ll have an application with a navigation that will display your components by route.

      React Router is a declarative routing framework. That means that you will configure the routes using standard React components. There are a few advantages to this approach. First, it follows the standard declaractive nature of React code. You don’t need to add a lot of code in componentDidMount methods or inside a useEffect Hook; your routes are components. Second, you can intuitively place routes inside of a component with other components serving as a template. As you read the code, you’ll find exactly where the dynamic components will fit in relation to the global views such as navigation or footers.

      To start adding routes, open App.js:

      • nano src/components/App/App.js

      The <h1> tag is going to serve as a global page title. Since you want it to appear on every page, configure the router after the tag.

      Import BrowserRouter, Route, and Switch from react-router-dom. BrowserRouter will be the base configuration. Switch will wrap the dynamic routes and the Route component will configure specific routes and wrap the component that should render:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <Manatee />
          </div>
        );
      }
      
      export default App;
      

      Add the BrowserRouter component to create a base router. Anything outside of this component will render on every page, so place it after your <h1> tag. In addition, if you have site-wide context that you want to use, or some other store such as Redux, place those components outside the router. This will make them available to all components on any route:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <Manatee />
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Next, add the Switch component inside BrowserRouter. This component will activate the correct route, much like the JavaScript switch statement. Inside of Switch, add a Route component for each route. In this case, you’ll want the following routes: /manataee, /narwhal, and /whale. The Route component will take a path as a parameter and surround a child component. The child component will display when the route is active.

      Create a route for the path / and render the Manatee component:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <Switch>
                <Route path="/">
                  <Manatee />
                </Route>
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do the browser will reload and you’ll find the information for the manatee component:

      Manatee showing at route /

      If you try a different route such as http://localhost:3000/whale, you’ll still find the manatee component.

      Manatee on /whale route

      The Switch component will render the first route that matches that pattern. Any route will match /, so it will render on every page. That also means that order is important. Since the router will exit as soon as it finds a match, always put a more specific route before a less specific route. In other words, /whale would go before / and /whale/beluga would go before /whale.

      If you want the route to match only the route as written and not any child routes, you can add the exact prop. For example, <Route exact path="https://www.digitalocean.com/manatee"> would match /manatee, but not /manatee/african.

      Update the route for the Manatee component to /manatee, then import the remaining components and create a route for each:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <Switch>
                <Route path="https://www.digitalocean.com/manatee">
                  <Manatee />
                </Route>
                <Route path="https://www.digitalocean.com/narwhal">
                  <Narwhal />
                </Route>
                <Route path="https://www.digitalocean.com/whale">
                  <Whale />
                </Route>
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, the browser will refresh. If you visit http://localhost:3000/, only the <h1> tag will render, because no routes match any of the Route components:

      No component on /

      If you visit http://localhost:3000/whale, you’ll find the Whale component:

      Whale on /whale route

      Now that you have some components, create navigation for a user to move between pages.

      Use the <nav> element to denote that you are creating a navigation portion of the page. Then add an unordered list (<ul>) with a list item (<li>) and a hyperlink (<a>) for each mammal:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <nav>
              <ul>
                <li><a href="https://www.digitalocean.com/manatee">Manatee</a></li>
                <li><a href="https://www.digitalocean.com/narwhal">Narwhal</a></li>
                <li><a href="https://www.digitalocean.com/whale">Whale</a></li>
              </ul>
            </nav>
            <BrowserRouter>
              ...
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, the browser will refresh, but there will be a problem. Since you are using the native browser links—<a> tags—you will get the default browser behavior any time you click on a link. That means any time you click on a link, you’ll trigger a full page refresh.

      Notice that the network will reload all of the JavaScript files when you click a link. That’s a big performance cost for your users.

      Browser refresh on link click

      At this point, you could add a click event handler on each link and prevent the default action. That would be a lot of work. Instead, React Router has a special component called Link that will handle the work for you. It will create a link tag, but prevent the default brower behavior while pushing the new location.

      In App.js, import Link from react-router-dom. Then replace each <a> with a Link. You’ll also need to change the href attribute to the to prop.

      Finally, move the <nav> component inside of the BrowserRouter. This ensures that the Link component is controlled by react-router:

      router-tutorial/src/components/App/App.js

      
      import React from 'react';
      import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <nav>
                <ul>
                  <li><Link to="https://www.digitalocean.com/manatee">Manatee</Link></li>
                  <li><Link to="https://www.digitalocean.com/narwhal">Narwhal</Link></li>
                  <li><Link to="https://www.digitalocean.com/whale">Whale</Link></li>
                </ul>
              </nav>
              <Switch>
                <Route path="https://www.digitalocean.com/manatee">
                  <Manatee />
                </Route>
                <Route path="https://www.digitalocean.com/narwhal">
                  <Narwhal />
                </Route>
                <Route path="https://www.digitalocean.com/whale">
                  <Whale />
                </Route>
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, the browser will refresh. When you click links, the page will not refresh and the browser will not reload the JavaScript code:

      No refresh on link click

      In this step you added React Router to your current project. You created a route for each component and you added a navigation using the Link component to switch between routes without a page refresh.

      In the next step, you’ll add more complex routes that render different components using URL parameters.

      Step 3 — Accessing Route Data with Hooks

      In this step, you’ll use URL queries and parameters to create dynamic routes. You’ll learn how to pull information from search parameters with the useLocation Hook and how to read information from dynamic URLs using the useParams Hook.

      By the end of this step, you’ll know how to access route information inside of your components and how you can use that information to dynamically load components.

      Suppose you wanted to add another level to your marine mammal application. There are many types of whales, and you could display information about each one. You have two choices of how to accomplish this: You could use the current route and add a specific whale type with search parameters, such as ?type=beluga. You could also create a new route that includes the specific name after the base URL, such as /whale/beluga. This tutorial will start with search parameters, since they are flexible and can handle multiple, different queries.

      First, make new components for different whale species.

      Open a new file Beluga.js in your text editor:

      • nano src/components/Whale/Beluga.js

      Add an <h3> tag with the name Beluga:

      router-tutorial/src/components/Whale/Beluga.js

      import React from 'react';
      
      export default function Beluga() {
        return(
          <h3>Beluga</h3>
        );
      }
      

      Do the same thing for a blue whale. Open a new file Blue.js in your text editor:

      • nano src/components/Whale/Blue.js

      Add an <h3> tag with the name Blue:

      router-tutorial/src/components/Whale/Blue.js

      import React from 'react';
      
      export default function Blue() {
        return(
          <h3>Blue</h3>
        );
      }
      

      Save and close the file.

      Passing Additional Information with Search Parameters

      Next, you are going to pass the whale information as a search parameter. This will let you pass information without needing to create a new URL.

      Open App.js so you can add new links:

      • nano src/components/App/App.js

      Add two new links, one to /whale?type=beluga and one for /whale?type=blue:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <nav>
                <ul>
                  <li><Link to="https://www.digitalocean.com/manatee">Manatee</Link></li>
                  <li><Link to="https://www.digitalocean.com/narwhal">Narwhal</Link></li>
                  <li><Link to="https://www.digitalocean.com/whale">Whale</Link></li>
                  <li><Link to="/whale?type=beluga">Beluga Whale</Link></li>
                  <li><Link to="/whale?type=blue">Blue Whale</Link></li>
                </ul>
              </nav>
              <Switch>
                ...
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save and close the file.

      If you click on the links, you’ll still see the regular whale page. This shows that the standard route is still working correctly:

      Beluga router with whale page

      Since you are correctly rendering the Whale component, you’ll need to update the component to pull the search query out of the URL and use it to render the correct child component.

      Open Whale.js:

      • nano src/components/Whale/Whale.js

      First, import the Beluga and Blue components. Next, import a Hook called useLocation from react-router-dom:

      router-tutorial/src/components/Whale/Whale.js

      import React from 'react';
      import { useLocation } from 'react-router-dom';
      import Beluga from './Beluga';
      import Blue from './Blue';
      
      export default function Whale() {
        return <h2>Whale</h2>;
      }
      

      The useLocation Hook pulls the location information from your page. This is not unique to React Router. The location object is a standard object on all browsers. If you open your browser console and type window.location, you’ll get an object with information about your URL.

      Window location in console

      Notice that the location information includes search, but also includes other information, such as the pathname and the full href. The useLocation Hook will provide this information for you. Inside of Whale.js, call the useLocation Hook. Destructure the result to pull out the search field. This will be a parameter string, such as ?type=beluga:

      router-tutorial/src/components/Whale/Whale.js

      import React from 'react';
      import { useLocation } from 'react-router-dom';
      import Beluga from './Beluga';
      import Blue from './Blue';
      
      export default function Whale() {
        const { search } = useLocation();
        return <h2>Whale</h2>;
      }
      

      There are a number of libraries, such as query-string, that can parse the search for you and convert it into an object that is easier to read and update. In this example, you can use a regular expression to pull out the information about the whale type.

      Use the .match method on the search string to pull out the type: search.match(/type=(.*)/). The parentheses inside the regular expression will capture the match into a results array. The first item in the array is the full match: type=beluga. The second item is the information from the parentheses: beluga.

      Use the data from the .match method to render the correct child component:

      router-tutorial/src/components/Whale/Whale.js

      
      import React from 'react';
      import { useLocation } from 'react-router-dom';
      import Beluga from './Beluga';
      import Blue from './Blue';
      
      export default function Whale() {
        const { search } = useLocation();
        const match = search.match(/type=(.*)/);
        const type = match?.[1];
      
        return (
          <>
            <h2>Whale</h2>
            {type === 'beluga' && <Beluga />}
            {type === 'blue' && <Blue />}
          </>
        );
      }
      

      The symbol ?. is called optional chaining. If the value exists, it returns the value. Otherwise, it will return undefined. This will protect your component in instances where the search parameter is empty.

      Save the file. When you do, the browser will refresh and will render different whales:

      Different whales with search params

      Accessing URL Parameters

      Search parameters work, but they aren’t the best solution in this case. Generally, you’d use search parameters to refine a page: toggling information or loading specific data. In this case, you are not refining a page; you are creating a new static page. Fortunately, React Router provides a way to create dynamic URLs that preserve variable data called URL Parameters.

      Open App.js:

      • nano src/components/App/App.js

      Instead of passing the whale information as a search, you will add it directly to the URL itself. That means that you will move the seach into the URL instead of adding it after a ?. For example, the query/whale?type=blue will now be /whale/blue:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <nav>
                <ul>
                  <li><Link to="https://www.digitalocean.com/manatee">Manatee</Link></li>
                  <li><Link to="https://www.digitalocean.com/narwhal">Narwhal</Link></li>
                  <li><Link to="https://www.digitalocean.com/whale">Whale</Link></li>
                  <li><Link to="/whale/beluga">Beluga Whale</Link></li>
                  <li><Link to="/whale/blue">Blue Whale</Link></li>
                </ul>
              </nav>
              <Switch>
                <Route path="https://www.digitalocean.com/manatee">
                  <Manatee />
                </Route>
                <Route path="https://www.digitalocean.com/narwhal">
                  <Narwhal />
                </Route>
                <Route path="https://www.digitalocean.com/whale">
                  <Whale />
                </Route>
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Now you need to create a new route that can capture both /whale/beluga and /whale/blue. You could add them by hand, but this wouldn’t work in situations where you don’t know all the possibilities ahead of time, such as when you have a list of users or other dynamic data.

      Instead of making a route for each one, add a URL param to the current path. The URL param is a keyword prefaced with a colon. React Router will use the parameter as a wildcard and will match any route that contains that pattern.

      In this case, create a keyword of :type. The full path will be /whale/:type. This will match any route that starts with /whale and it will save the variable information inside a parameter variable called type. This route will not match /whale, since that does not contain an additional parameter.

      You can either add /whale as a route after the new route or you can add it before the route of /whale/:type with the exact keyword.

      Add a new route of /whale/:type and add an exact property to the current route:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <nav>
                <ul>
                  <li><Link to="https://www.digitalocean.com/manatee">Manatee</Link></li>
                  <li><Link to="https://www.digitalocean.com/narwhal">Narwhal</Link></li>
                  <li><Link to="https://www.digitalocean.com/whale">Whale</Link></li>
                  <li><Link to="/whale/beluga">Beluga Whale</Link></li>
                  <li><Link to="/whale/blue">Blue Whale</Link></li>
                </ul>
              </nav>
              <Switch>
                <Route path="https://www.digitalocean.com/manatee">
                  <Manatee />
                </Route>
                <Route path="https://www.digitalocean.com/narwhal">
                  <Narwhal />
                </Route>
                <Route exact path="https://www.digitalocean.com/whale">
                  <Whale />
                </Route>
                <Route path="/whale/:type">
                  <Whale />
                </Route>
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save and close the file. Now that you are passing new information, you need to access it and use the information to render dynamic components.

      Open Whale.js:

      • nano src/components/Whale/Whale.js

      Import the useParams Hook. This will connect to your router and pull out any URL parameters into an object. Destructure the object to pull out the type field. Remove the code for parsing the search and use the parameter to conditionally render child components:

      router-tutorial/src/components/Whale/Whale.js

      import React from 'react';
      import { useParams } from 'react-router-dom';
      import Beluga from './Beluga';
      import Blue from './Blue';
      
      export default function Whale() {
        const { type } = useParams();
      
        return (
          <>
            <h2>Whale</h2>
            {type === 'beluga' && <Beluga />}
            {type === 'blue' && <Blue />}
          </>
        );
      }
      

      Save and close the file. When you do, the browser will refresh and you’ll be able to use the new URLs, such as http://localhost:3000/whale/beluga:

      Beluga whale parameter

      URL parameters are a clear way to pass conditional data. They are not as flexible as search parameters, which can be combined or reordered, but they are more clear and easier for search engines to index.

      In this step you passed variable data using search parameters and URL parameters. You also used the useLocation and useParams Hooks to pull information out and to render conditional components.

      But there is one problem: The list of routes is getting long and you are starting to get near duplicates with the /whale and /whale/:type routes. React Router lets you split out child routes directly in the component, which means you don’t need to have the whole list in a single component. In the next step, you’ll render routes directly inside of child components.

      Step 4 — Nesting Routes

      Routes can grow and become more complex. React Router uses nested routes to render more specific routing information inside of child components. In this step, you’ll use nested routes and add routes in different components. By the end of this step, you’ll have different options for rendering your information.

      In the last step, you added routes inside of App.js. This has some advantages: It keeps all routes in one place, essentially creating a site map for your application. But it can easily grow and be difficult to read and maintain. Nested routes group your routing information directly in the components that will render other components, giving you the ability to create mini-templates throughout your application.

      Open App.js:

      • nano src/components/App/App.js

      Remove the /whale/:type route and remove the exact prop so you only have a whale route:

      router-tutorial/src/components/App/App.js

      import React from 'react';
      import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
      import './App.css';
      
      import Manatee from '../Manatee/Manatee';
      import Narwhal from '../Narwhal/Narwhal';
      import Whale from '../Whale/Whale';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>Marine Mammals</h1>
            <BrowserRouter>
              <nav>
                <ul>
                  <li><Link to="https://www.digitalocean.com/manatee">Manatee</Link></li>
                  <li><Link to="https://www.digitalocean.com/narwhal">Narwhal</Link></li>
                  <li><Link to="https://www.digitalocean.com/whale">Whale</Link></li>
                  <li><Link to="/whale/beluga">Beluga Whale</Link></li>
                  <li><Link to="/whale/blue">Blue Whale</Link></li>
                </ul>
              </nav>
              <Switch>
                <Route path="https://www.digitalocean.com/manatee">
                  <Manatee />
                </Route>
                <Route path="https://www.digitalocean.com/narwhal">
                  <Narwhal />
                </Route>
                <Route path="https://www.digitalocean.com/whale">
                  <Whale />
                </Route>
              </Switch>
            </BrowserRouter>
          </div>
        );
      }
      
      export default App;
      

      Save and close the file.

      Next, open Whale.js. This is where you will add the nested route.

      • nano src/components/Whale/Whale.js

      You will need to do two things. First, get the current path with the useRouteMatch Hook. Next, render the new <Switch> and <Route> components to display the correct components.

      Import useRouteMatch. This will return an object that contains the path and the url. Destructure the object to get the path. You’ll use this as the basis for your new routes:

      router-tutorial/src/components/Whale/Whale.js

      import React from 'react';
      import { useRouteMatch } from 'react-router-dom';
      import Beluga from './Beluga';
      import Blue from './Blue';
      
      export default function Whale() {
        const { path } = useRouteMatch();
      
        return (
          <>
            <h2>Whale</h2>
            {type === 'beluga' && <Beluga />}
            {type === 'blue' && <Blue />}
          </>
        );
      }
      

      Next, import Switch and Route so you can add in new routes. Your new routes will be the same as you made in App.js, but you do not need to wrap them with BrowserRouter. Add the new routes, but prefix the route with the path. The new component will render exactly where you place them, so add the new routes after the <h2>:

      router-tutorial/src/components/Whale/Whale.js

      import React from 'react';
      import { Switch, Route, useRouteMatch } from 'react-router-dom';
      import Beluga from './Beluga';
      import Blue from './Blue';
      
      export default function Whale() {
        const { path } = useRouteMatch();
        return (
          <>
            <h2>Whale</h2>
            <Switch>
              <Route path={`${path}/beluga`}>
                <Beluga />
              </Route>
              <Route path={`${path}/blue`}>
                <Blue />
              </Route>
            </Switch>
          </>
        );
      }
      

      Save the file. When you do, the browser will refresh and you’ll be able to visit the child routes.

      Visiting child routes

      This is a little extra code, but it keeps the child routes situated with their parent. Not all projects use nested routes: some prefer having an explicit list. It is a matter of team preference and consistency. Choose the option that is best for your project, and you can always refactor later.

      In this step, you added nested routes to your project. You pulled out the current path with the useRouteMatch Hook and added new routes in a component to render the new components inside of a base component.

      Conclusion

      React Router is an important part of any React project. When you build single page applications, you’ll use routes to separate your application into usable pieces that users can access easily and consistently.

      As you start to separate your components into routes, you’ll be able to take advantage of code splitting, preserving state via query parameters, and other tools to improve the user experience.

      If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link

      How To Handle Async Data Loading, Lazy Loading, and Code Splitting with React


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      As a JavaScript web developer, asynchronous code gives you the ability to run some parts of your code while other parts are still waiting for data or resolving. This means that important parts of your app will not have to wait for less important parts before they render. With asynchronous code you can also update your application by requesting and displaying new information, giving users a smooth experience even when long functions and requests are processing in the background.

      In React development, asynchronous programming presents unique problems. When you use React functional components for example, asynchronous functions can create infinite loops. When a component loads, it can start an asynchronous function, and when the asynchronous function resolves it can trigger a re-render that will cause the component to recall the asynchronous function. This tutorial will explain how to avoid this with a special Hook called useEffect, which will run functions only when specific data changes. This will let you run your asynchronous code deliberately instead of on each render cycle.

      Asynchronous code is not just limited to requests for new data. React has a built-in system for lazy loading components, or loading them only when the user needs them. When combined with the default webpack configuration in Create React App, you can split up your code, reducing a large application into smaller pieces that can be loaded as needed. React has a special component called Suspense that will display placeholders while the browser is loading your new component. In future versions of React, you’ll be able to use Suspense to load data in nested components without render blocking.

      In this tutorial, you’ll handle asynchronous data in React by creating an app that displays information on rivers and simulates requests to Web APIs with setTimeout. By the end of this tutorial, you’ll be able to load asynchronous data using the useEffect Hook. You’ll also be able to safely update the page without creating errors if the component unmounts before data resolution. Finally, you’ll split a large application into smaller parts using code splitting.

      Prerequisites

      Step 1 — Loading Asynchronous Data with useEffect

      In this step, you’ll use the useEffect Hook to load asynchronous data into a sample application. You’ll use the Hook to prevent unnecessary data fetching, add placeholders while the data is loading, and update the component when the data resolves. By the end of this step, you’ll be able to load data with useEffect and set data using the useState Hook when it resolves.

      To explore the topic, you are going to create an application to display information about the longest rivers in the world. You’ll load data using an asynchronous function that simulates a request to an external data source.

      First, create a component called RiverInformation. Make the directory:

      • mkdir src/components/RiverInformation

      Open RiverInformation.js in a text editor:

      • nano src/components/RiverInformation/RiverInformation.js

      Then add some placeholder content:

      async-tutorial/src/components/RiverInformation/RiverInformation.js

      import React from 'react';
      
      export default function RiverInformation() {
        return(
          <div>
            <h2>River Information</h2>
          </div>
        )
      }
      

      Save and close the file. Now you need to import and render the new component to your root component. Open App.js:

      • nano src/components/App/App.js

      Import and render the component by adding in the highlighted code:

      async-tutorial/src/components/App/App.js

      import React from 'react';
      import './App.css';
      import RiverInformation from '../RiverInformation/RiverInformation';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>World's Longest Rivers</h1>
            <RiverInformation />
          </div>
        );
      }
      
      export default App;
      

      Save and close the file.

      Finally, in order to make the app easier to read, add some styling. Open App.css:

      • nano src/components/App/App.css

      Add some padding to the wrapper class by replacing the CSS with the following:

      async-tutorial/src/components/App/App.css

      .wrapper {
          padding: 20px
      }
      

      Save and close the file. When you do, the browser will refresh and render the basic components.

      Basic Component, 1

      In this tutorial, you’ll make generic services for returning data. A service refers to any code that can be reused to accomplish a specific task. Your component doesn’t need to know how the service gets its information. All it needs to know is that the service will return a Promise. In this case, the data request will be simulated with setTimeout, which will wait for a specified amount of time before providing data.

      Create a new directory called services under the src/ directory:

      This directory will hold your asynchronous functions. Open a file called rivers.js:

      • nano src/services/rivers.js

      Inside the file, export a function called getRiverInformation that returns a promise. Inside the promise, add a setTimeout function that will resolve the promise after 1500 milliseconds. This will give you some time to see how the component will render while waiting for data to resolve:

      async-tutorial/src/services/rivers.js

      export function getRiverInformation() {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve({
              continent: 'Africa',
              length: '6,650 km',
              outflow: 'Mediterranean'
            })
          }, 1500)
        })
      }
      

      In this snippet, you are hard-coding the river information, but this function will be similar to any asynchronous functions you may use, such as an API call. The important part is that the code returns a promise.

      Save and close the file.

      Now that you have a service that returns the data, you need to add it to your component. This can sometimes lead to a problem. Suppose you called the asynchronous function inside of your component and then set the data to a variable using the useState Hook. The code will be like this:

      import React, { useState } from 'react';
      import { getRiverInformation } from '../../services/rivers';
      
      export default function RiverInformation() {
        const [riverInformation, setRiverInformation] = useState({});
      
        getRiverInformation()
        .then(d => {
          setRiverInformation(d)
        })
      
        return(
          ...
        )
      }
      

      When you set the data, the Hook change will trigger a components re-render. When the component re-renders, the getRiverInformation function will run again, and when it resolves it will set the state, which will trigger another re-render. The loop will continue forever.

      To solve this problem, React has a special Hook called useEffect that will only run when specific data changes.

      The useEffect Hook accepts a function as the first argument and an array of triggers as the second argument. The function will run on the first render after the layout and paint. After that, it will only run if one of the triggers changes. If you supply an empty array, it will only run one time. If you do not include an array of triggers, it will run after every render.

      Open RiverInformation.js:

      • nano src/components/RiverInformation/RiverInformation.js

      Use the useState Hook to create a variable called riverInformation and a function called setRiverInformation. You’ll update the component by setting the riverInformation when the asynchronous function resolves. Then wrap the getRiverInformation function with useEffect. Be sure to pass an empty array as a second argument. When the promise resolves, update the riverInformation with the setRiverInformation function:

      async-tutorial/src/components/RiverInformation/RiverInformation.js

      import React, { useEffect, useState } from 'react';
      import { getRiverInformation } from '../../services/rivers';
      
      export default function RiverInformation() {
        const [riverInformation, setRiverInformation] = useState({});
      
        useEffect(() => {
         getRiverInformation()
         .then(data =>
           setRiverInformation(data)
         );
        }, [])
      
      
        return(
          <div>
            <h2>River Information</h2>
            <ul>
              <li>Continent: {riverInformation.continent}</li>
              <li>Length: {riverInformation.length}</li>
              <li>Outflow: {riverInformation.outflow}</li>
            </ul>
          </div>
        )
      }
      

      After the asynchronous function resolves, update an unordered list with the new information.

      Save and close the file. When you do the browser will refresh and you’ll find the data after the function resolves:

      River Information Updating After Load, 2

      Notice that the component renders before the data is loaded. The advantage with asynchronous code is that it won’t block the initial render. In this case, you have a component that shows the list without any data, but you could also render a spinner or a scalable vector graphic (SVG) placeholder.

      There are times when you’ll only need to load data once, such as if you are getting user information or a list of resources that never change. But many times your asynchronous function will require some arguments. In those cases, you’ll need to trigger your use useEffect Hook whenever the data changes.

      To simulate this, add some more data to your service. Open rivers.js:

      • nano src/services/rivers.js

      Then add an object that contains data for a few more rivers. Select the data based on a name argument:

      async-tutorial/src/services/rivers.js

      const rivers = {
       nile: {
         continent: 'Africa',
         length: '6,650 km',
         outflow: 'Mediterranean'
       },
       amazon: {
         continent: 'South America',
         length: '6,575 km',
         outflow: 'Atlantic Ocean'
       },
       yangtze: {
         continent: 'Asia',
         length: '6,300 km',
         outflow: 'East China Sea'
       },
       mississippi: {
         continent: 'North America',
         length: '6,275 km',
         outflow: 'Gulf of Mexico'
       }
      }
      
      export function getRiverInformation(name) {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve(
              rivers[name]
            )
          }, 1500)
        })
      }
      

      Save and close the file. Next, open App.js so you can add more options:

      • nano src/components/App/App.js

      Inside App.js, create a stateful variable and function to hold the selected river with the useState Hook. Then add a button for each river with an onClick handler to update the selected river. Pass the river to RiverInformation using a prop called name:

      async-tutorial/src/components/App/App.js

      import React, { useState } from 'react';
      import './App.css';
      import RiverInformation from '../RiverInformation/RiverInformation';
      
      function App() {
        const [river, setRiver] = useState('nile');
        return (
          <div className="wrapper">
            <h1>World's Longest Rivers</h1>
            <button onClick={() => setRiver('nile')}>Nile</button>
            <button onClick={() => setRiver('amazon')}>Amazon</button>
            <button onClick={() => setRiver('yangtze')}>Yangtze</button>
            <button onClick={() => setRiver('mississippi')}>Mississippi</button>
            <RiverInformation name={river} />
          </div>
        );
      }
      
      export default App;
      

      Save and close the file. Next, open RiverInformation.js:

      • nano src/components/RiverInformation/RiverInformation.js

      Pull in the name as a prop and pass it to the getRiverInformation function. Be sure to add name to the array for useEffect, otherwise it will not rerun:

      async-tutorial/src/components/RiverInformation/RiverInformation.js

      import React, { useEffect, useState } from 'react';
      import PropTypes from 'prop-types';
      import { getRiverInformation } from '../../services/rivers';
      
      export default function RiverInformation({ name }) {
        const [riverInformation, setRiverInformation] = useState({});
      
        useEffect(() => {
          getRiverInformation(name)
          .then(data =>
            setRiverInformation(data)
          );
        }, [name])
      
      
        return(
          <div>
            <h2>River Information</h2>
            <ul>
              <li>Continent: {riverInformation.continent}</li>
              <li>Length: {riverInformation.length}</li>
              <li>Outflow: {riverInformation.outflow}</li>
            </ul>
          </div>
        )
      }
      
      RiverInformation.propTypes = {
       name: PropTypes.string.isRequired
      }
      

      In this code, you also added a weak typing system with PropTypes, which will make sure that the prop is a string.

      Save the file. When you do, the browser will refresh and you can select different rivers. Notice the delay between when you click and when the data renders:

      Update river information, 3

      If you had left out the name prop from the useEffect array, you would receive a build error in the browser console. It would be something like this:

      Error

      Compiled with warnings. ./src/components/RiverInformation/RiverInformation.js Line 13:6: React Hook useEffect has a missing dependency: 'name'. Either include it or remove the dependency array react-hooks/exhaustive-deps Search for the keywords to learn more about each warning. To ignore, add // eslint-disable-next-line to the line before.

      This error tells you that the function in your effect has dependencies that you are not explicitly setting. In this situation, it’s clear that the effect wouldn’t work, but there are times when you may be comparing prop data to stateful data inside the component, which makes it possible to lose track of items in the array.

      The last thing to do is to add some defensive programming to your component. This is a design principle that emphasizes high availability for your application. You want to ensure that your component will render even if the data is not in the correct shape or if you do not get any data at all from an API request.

      As your app is now, the effect will update the riverInformation with any type of data it receives. This will usually be an object, but in cases where it’s not, you can use optional chaining to ensure that you will not throw an error.

      Inside RiverInformation.js, replace the instance of an object dot chaining with optional chaining. To test if it works, remove the default object {} from the useState function:

      async-tutorial/src/components/RiverInformation/RiverInformation.js

      import React, { useEffect, useState } from 'react';
      import PropTypes from 'prop-types';
      import { getRiverInformation } from '../../services/rivers';
      
      export default function RiverInformation({ name }) {
        const [riverInformation, setRiverInformation] = useState();
      
        useEffect(() => {
          getRiverInformation(name)
          .then(data =>
            setRiverInformation(data)
          );
        }, [name])
      
        return(
          <div>
            <h2>River Information</h2>
            <ul>
              <li>Continent: {riverInformation?.continent}</li>
              <li>Length: {riverInformation?.length}</li>
              <li>Outflow: {riverInformation?.outflow}</li>
            </ul>
          </div>
        )
      }
      
      RiverInformation.propTypes = {
        name: PropTypes.string.isRequired
      }
      

      Save and close the file. When you do, the file will still load even though the code is referencing properties on undefined instead of an object:

      River Information Updating After Load, 4

      Defensive programming is usually considered a best practice, but it’s especially important on asynchronous functions such as API calls when you can’t guarantee a response.

      In this step, you called asynchronous functions in React. You used the useEffect Hook to fetch information without triggering re-renders and triggered a new update by adding conditions to the useEffect array.

      In the next step, you’ll make some changes to your app so that it updates components only when they are mounted. This will help your app avoid memory leaks.

      Step 2 — Preventing Errors on Unmounted Components

      In this step, you’ll prevent data updates on unmounted components. Since you can never be sure when data will resolve with asynchronous programming, there’s always a risk that the data will resolve after the component has been removed. Updating data on an unmounted component is inefficient and can introduce memory leaks in which your app is using more memory than it needs to.

      By the end of this step, you’ll know how to prevent memory leaks by adding guards in your useEffect Hook to update data only when the component is mounted.

      The current component will always be mounted, so there’s no chance that the code will try and update the component after it is removed from the DOM, but most components aren’t so reliable. They will be added and removed from the page as the user interacts with the application. If a component is removed from a page before the asynchronous function resolves, you can have a memory leak.

      To test out the problem, update App.js to be able to add and remove the river details.

      Open App.js:

      • nano src/components/App/App.js

      Add a button to toggle the river details. Use the useReducer Hook to create a function to toggle the details and a variable to store the toggled state:

      async-tutorial/src/components/App/App.js

      import React, { useReducer, useState } from 'react';
      import './App.css';
      import RiverInformation from '../RiverInformation/RiverInformation';
      
      function App() {
        const [river, setRiver] = useState('nile');
        const [show, toggle] = useReducer(state => !state, true);
        return (
          <div className="wrapper">
            <h1>World's Longest Rivers</h1>
            <div><button onClick={toggle}>Toggle Details</button></div>
            <button onClick={() => setRiver('nile')}>Nile</button>
            <button onClick={() => setRiver('amazon')}>Amazon</button>
            <button onClick={() => setRiver('yangtze')}>Yangtze</button>
            <button onClick={() => setRiver('mississippi')}>Mississippi</button>
            {show && <RiverInformation name={river} />}
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do the browse will reload and you’ll be able to toggle the details.

      Click on a river, then immediately click on the Toggle Details button to hide details. React will generate an error warning that there is a potential memory leak.

      Warning when component is updated after being removed, 5

      To fix the problem you need to either cancel or ignore the asynchronous function inside useEffect. If you are using a library such as RxJS, you can cancel an asynchronous action when the component unmounts by returning a function in your useEffect Hook. In other cases, you’ll need a variable to store the mounted state.

      Open RiverInformation.js:

      • nano src/components/RiverInformation/RiverInformation.js

      Inside the useEffect function, create a variable called mounted and set it to true. Inside the .then callback, use a conditional to set the data if mounted is true:

      async-tutorial/src/components/RiverInformation/RiverInformation.js

      
      import React, { useEffect, useState } from 'react';
      import PropTypes from 'prop-types';
      import { getRiverInformation } from '../../services/rivers';
      
      export default function RiverInformation({ name }) {
        const [riverInformation, setRiverInformation] = useState();
      
        useEffect(() => {
          let mounted = true;
          getRiverInformation(name)
          .then(data => {
            if(mounted) {
              setRiverInformation(data)
            }
          });
        }, [name])
      
      
        return(
          <div>
            <h2>River Information</h2>
            <ul>
              <li>Continent: {riverInformation?.continent}</li>
              <li>Length: {riverInformation?.length}</li>
              <li>Outflow: {riverInformation?.outflow}</li>
            </ul>
          </div>
        )
      }
      
      RiverInformation.propTypes = {
        name: PropTypes.string.isRequired
      }
      

      Now that you have the variable, you need to be able to flip it when the component unmounts. With the useEffect Hook, you can return a function that will run when the component unmounts. Return a function that sets mounted to false:

      async-tutorial/src/components/RiverInformation/RiverInformation.js

      
      import React, { useEffect, useState } from 'react';
      import PropTypes from 'prop-types';
      import { getRiverInformation } from '../../services/rivers';
      
      export default function RiverInformation({ name }) {
        const [riverInformation, setRiverInformation] = useState();
      
        useEffect(() => {
          let mounted = true;
          getRiverInformation(name)
          .then(data => {
            if(mounted) {
              setRiverInformation(data)
            }
          });
          return () => {
           mounted = false;
         }
        }, [name])
      
        return(
          <div>
            <h2>River Information</h2>
            <ul>
              <li>Continent: {riverInformation?.continent}</li>
              <li>Length: {riverInformation?.length}</li>
              <li>Outflow: {riverInformation?.outflow}</li>
            </ul>
          </div>
        )
      }
      
      RiverInformation.propTypes = {
        name: PropTypes.string.isRequired
      }
      

      Save the file. When you do, you’ll be able to toggle the details without an error.

      No warning when toggling, 6

      When you unmount, the component useEffect updates the variable. The asynchronous function will still resolve, but it won’t make any changes to unmounted components. This will prevent memory leaks.

      In this step, you made your app update state only when a component is mounted. You updated the useEffect Hook to track if the component is mounted and returned a function to update the value when the component unmounts.

      In the next step, you’ll asynchronously load components to split code into smaller bundles that a user will load as needed.

      Step 3 — Lazy Loading a Component with Suspense and lazy

      In this step, you’ll split your code with React Suspense and lazy. As applications grow, the size of the final build grows with it. Rather than forcing users to download the whole application, you can split the code into smaller chunks. React Suspense and lazy work with webpack and other build systems to split your code into smaller pieces that a user will be able to load on demand. In the future, you will be able to use Suspense to load a variety of data, including API requests.

      By the end of this step, you’ll be able to load components asynchronously, breaking large applications into smaller, more focused chunks.

      So far you’ve only worked with asynchronously loading data, but you can also asynchronously load components. This process, often called code splitting, helps reduce the size of your code bundles so your users don’t have to download the full application if they are only using a portion of it.

      Most of the time, you import code statically, but you can import code dynamically by calling import as a function instead of a statement. The code would be something like this:

      import('my-library')
      .then(library => library.action())
      

      React gives you an additional set of tools called lazy and Suspense. React Suspense will eventually expand to handle data loading, but for now you can use it to load components.

      Open App.js:

      • nano src/components/App/App.js

      Then import lazy and Suspense from react:

      async-tutorial/src/components/App/App.js

      import React, { lazy, Suspense, useReducer, useState } from 'react';
      import './App.css';
      import RiverInformation from '../RiverInformation/RiverInformation';
      
      function App() {
        const [river, setRiver] = useState('nile');
        const [show, toggle] = useReducer(state => !state, true);
        return (
          <div className="wrapper">
            <h1>World's Longest Rivers</h1>
            <div><button onClick={toggle}>Toggle Details</button></div>
            <button onClick={() => setRiver('nile')}>Nile</button>
            <button onClick={() => setRiver('amazon')}>Amazon</button>
            <button onClick={() => setRiver('yangtze')}>Yangtze</button>
            <button onClick={() => setRiver('mississippi')}>Mississippi</button>
            {show && <RiverInformation name={river} />}
          </div>
        );
      }
      
      export default App;
      

      lazy and Suspsense have two distinct jobs. You use the lazy function to dynamically import the component and set it to a variable. Suspense is a built-in component you use to display a fallback message while the code is loading.

      Replace import RiverInformation from '../RiverInformation/RiverInformation'; with a call to lazy. Assign the result to a variable called RiverInformation. Then wrap {show && <RiverInformation name={river} />} with the Suspense component and a <div> with a message of Loading Component to the fallback prop:

      async-tutorial/src/components/App/App.js

      import React, { lazy, Suspense, useReducer, useState } from 'react';
      import './App.css';
      const RiverInformation = lazy(() => import('../RiverInformation/RiverInformation'));
      
      function App() {
        const [river, setRiver] = useState('nile');
        const [show, toggle] = useReducer(state => !state, true);
        return (
          <div className="wrapper">
            <h1>World's Longest Rivers</h1>
            <div><button onClick={toggle}>Toggle Details</button></div>
            <button onClick={() => setRiver('nile')}>Nile</button>
            <button onClick={() => setRiver('amazon')}>Amazon</button>
            <button onClick={() => setRiver('yangtze')}>Yangtze</button>
            <button onClick={() => setRiver('mississippi')}>Mississippi</button>
            <Suspense fallback={<div>Loading Component</div>}>
              {show && <RiverInformation name={river} />}
            </Suspense>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, reload the page and you’ll find that the component is dynamically loaded. If you want to see the loading message, you can throttle the response in the Chrome web browser.

      Component Loading

      If you navigate to the Network tab in Chrome or Firefox, you’ll find that the code is broken into different chunks.

      Chunks

      Each chunk gets a number by default, but with Create React App combined with webpack, you can set the chunk name by adding a comment by the dynamic import.

      In App.js, add a comment of /* webpackChunkName: "RiverInformation" */ inside the import function:

      async-tutorial/src/components/App/App.js

      import React, { lazy, Suspense, useReducer, useState } from 'react';
      import './App.css';
      const RiverInformation = lazy(() => import(/* webpackChunkName: "RiverInformation" */ '../RiverInformation/RiverInformation'));
      
      function App() {
        const [river, setRiver] = useState('nile');
        const [show, toggle] = useReducer(state => !state, true);
        return (
          <div className="wrapper">
            <h1>World's Longest Rivers</h1>
            <div><button onClick={toggle}>Toggle Details</button></div>
            <button onClick={() => setRiver('nile')}>Nile</button>
            <button onClick={() => setRiver('amazon')}>Amazon</button>
            <button onClick={() => setRiver('yangtze')}>Yangtze</button>
            <button onClick={() => setRiver('mississippi')}>Mississippi</button>
            <Suspense fallback={<div>Loading Component</div>}>
              {show && <RiverInformation name={river} />}
            </Suspense>
          </div>
        );
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will refresh and the RiverInformation chunk will have a unique name.

      River Information Chunk

      In this step, you asynchronously loaded components. You used lazy and Suspense to dynamically import components and to show a loading message while the component loads. You also gave custom names to webpack chunks to improve readability and debugging.

      Conclusion

      Asynchronous functions create efficient user-friendly applications. However, their advantages come with some subtle costs that can evolve into bugs in your program. You now have tools that will let you split large applications into smaller pieces and load asynchronous data while still giving the user a visible application. You can use the knowledge to incorporate API requests and asynchronous data manipulations into your applications creating fast and reliable user experiences.

      If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link