One place for hosting & domains

      Handle

      How To Handle Errors in a Flask Application


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Flask is a lightweight Python web framework that provides useful tools and features for creating web applications in the Python Language.

      When you’re developing a web application, you will inevitably run into situations where your application behaves in a way contrary to what you expected. You might misspell a variable, misuse a for loop, or construct an if statement in a way that raises a Python exception, like calling a function before declaring it, or simply looking for a page that doesn’t exist. You’ll find it easier and smoother to develop your Flask applications if you learn how to handle errors and exceptions properly.

      In this tutorial, you’ll build a small web application that demonstrates how to handle common errors one encounters when developing a web application. You’ll create custom error pages, use the Flask debugger to troubleshoot exceptions, and use logging to track events in your application.

      Prerequisites

      Step 1 — Using The Flask Debugger

      In this step, you’ll create an application that has a few errors and run it without debug mode to see how the application responds. Then you’ll run it with debug mode on and use the debugger to troubleshoot application errors.

      With your programming environment activated and Flask installed, open a file called app.py for editing inside your flask_app directory:

      Add the following code inside the app.py file:

      flask_app/app.py

      from flask import Flask
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def index():
          return render_template('index.html')
      

      In the above code, you first import the Flask class from the flask package. Then you create a Flask application instance called app. You use the @app.route() decorator to create a view function called index(), which calls the render_template() function as the return value, which in turn renders a template called index.html. There are two errors in this code: the first is that you did not import the render_template() function, and the second one is that the index.html template file does not exist.

      Save and close the file.

      Next, inform Flask about the application using the FLASK_APP environment variable using the following command (on Windows, use set instead of export):

      Then run the application server using the flask run command:

      You will see the following information in your terminal:

      Output

      * Serving Flask app 'app' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

      This output provides the following information:

      • The Flask application being served (app.py in this case)
      • The environment, which is production here. The warning message stresses that this server is not for a production deployment. You’re using this server for development, so you can ignore this warning, but for more information, see the Deployment Options page on the Flask documentation. You can also check out this Flask deployment tutorial with Gunicorn, or this one with uWSGI, or you can use DigitalOcean App Platform to deploy your Flask application by following the How To Deploy a Flask App Using Gunicorn to App Platform tutorial.

      • The debug mode is off, which means that the Flask debugger is not running, and you won’t receive helpful error messages in your application. In a production environment, displaying detailed errors exposes your application to security vulnerabilities.

      • The server is running on the http://127.0.0.1:5000/ URL. To stop the server, use CTRL+C, but don’t do that just yet.

      Now, visit the index page using your browser:

      http://127.0.0.1:5000/
      

      You will see a message that looks like the following:

      Output

      Internal Server Error The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

      This is the 500 Internal Server Error, which is a server error response that indicates that the server encountered an internal error in the application code.

      In the terminal, you’ll see the following output:

      Output

      [2021-09-12 15:16:56,441] ERROR in app: Exception on / [GET] Traceback (most recent call last): File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app response = self.full_dispatch_request() File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request rv = self.handle_user_exception(e) File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request rv = self.dispatch_request() File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/home/abd/python/flask/series03/flask_app/app.py", line 8, in index return render_template('index.html') NameError: name 'render_template' is not defined 127.0.0.1 - - [12/Sep/2021 15:16:56] "GET / HTTP/1.1" 500 -

      The traceback above goes through the code that triggered the internal server error. The line NameError: name 'render_template' is not defined gives the root cause of the problem: the render_template() function has not been imported.

      As you can see here, you have to go to the terminal to troubleshoot errors, which is not convenient.

      You can have a better troubleshooting experience by enabling the debug mode in your development server. To do so, stop the server with CTRL+C and set the environment variable FLASK_ENV to development, so you can run the application in development mode (which enables the debugger), using the following command (on Windows, use set instead of export):

      • export FLASK_ENV=development

      Run the development server:

      You’ll see an output similar to the following in the terminal:

      Output

      * Serving Flask app 'app' (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 120-484-907

      Here you see that the environment is now development, debug mode is on, and the debugger is active. The Debugger PIN is a PIN you need to unlock the console in your browser (an interactive python shell you can access by clicking the little terminal icon encircled in the image below).

      Refresh the index page on your browser and you’ll see the following page:

      The Flask Debugger

      Here, you see the error message displayed in a manner that’s easier to understand. The first heading gives you the name of the Python exception that caused the problem (NameError in this case). The second line gives you the direct reason (render_template() is not defined, which means it’s not imported in this case). Following that, you have the traceback going through the inner Flask code that was executed. Read the traceback from the bottom upward, because the last line in the traceback usually has the most useful information.

      Note:
      The circled terminal icon allows you to run Python code in the browser on different frames. This is useful for when you want to check the value of a variable the way you would do it in a Python interactive shell. When you click the terminal icon, you will need to type in the Debugger PIN code you got when you ran the server. You won’t need this interactive shell in this tutorial.

      To fix this NameError issue, leave the server running, open a new terminal window, activate your environment, and open your app.py file:

      Modify the file to look as follows:

      flask_app/app.py

      
      from flask import Flask, render_template
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def index():
          return render_template('index.html')
      

      Save and close the file.

      Here you imported the render_template() function that was missing.

      With the development server running, refresh the index page on your browser.

      This time you’ll see an error page with information that looks like so:

      Output

      jinja2.exceptions.TemplateNotFound jinja2.exceptions.TemplateNotFound: index.html

      This error message indicates that the index.html template does not exist.

      To fix this, you’ll create a base.html template file other templates will inherit from to avoid code repetition, then an index.html template that extends the base template.

      Create the templates directory, which is the directory where Flask looks for template files. Then open a base.html file using your favorite editor:

      • mkdir templates
      • nano templates/base.html

      Add the following code to your base.html file:

      flask_app/templates/base.html

      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for('index') }}">FlaskApp</a>
              <a href="#">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Save and close the file.

      This base template has all the HTML boilerplate you’ll need to reuse in your other templates. The title block will be replaced to set a title for each page, and the content block will be replaced with the content of each page. The navigation bar has two links, one for the index page where you use the url_for() helper function to link to the index() view function, and the other for an About page if you choose to include one in your application.

      Next, open a template file called index.html, which will inherit from the base template.

      • nano templates/index.html

      Add the following code to it:

      flask_app/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Index {% endblock %}</h1>
          <h2>Welcome to FlaskApp!</h2>
      {% endblock %}
      

      Save and close the file.

      In the code above, you extend the base template and override the content block. You then set a page title and display it in an H1 header using the title block, and display a greeting in an H2 header.

      With the development server running, refresh the index page on your browser.

      You’ll see that the application displays no more errors and the index page is displayed as expected.

      You’ve now used debug mode and seen how to handle error messages. Next, you’ll abort a request to respond with an error message of your choice, and see how to respond with custom error pages.

      Step 2 — Creating Custom Error Pages

      In this step, you’ll learn how to abort requests and respond with a 404 HTTP error message for when the user requests data that does not exist on the server. You will also learn how to create custom error pages for common HTTP errors, such as the 404 Not Found error, and the 500 Internal Server Error error.

      To demonstrate how to abort requests and respond with a custom 404 HTTP error page, you’ll create a page that displays a few messages. If the requested message does not exist, you’ll respond with a 404 error.

      First, open your app.py file to add a new route for the messages page:

      Add the following route at the end of the file:

      flask_app/app.py

      # ...
      
      @app.route('/messages/<int:idx>')
      def message(idx):
          messages = ['Message Zero', 'Message One', 'Message Two']
          return render_template('message.html', message=messages[idx])
      

      Save and close the file.

      In the route above, you have a URL variable idx. This is the index that will determine what message will be displayed. For example, if the URL is /messages/0, the first message (Message Zero) will be displayed. You use the int converter to accept only positive integers, because URL variables have string values by default.

      Inside the message() view function, you have a regular Python list called messages with three messages. (In a real-world scenario, these messages would come from a database, an API, or another external data source.) The function returns a call to the render_template() function with two arguments, message.html as the template file, and a message variable that will be passed to the template. This variable will have a list item from the messages list depending on the value of the idx variable in the URL.

      Next open a new message.html template file:

      • nano templates/message.html

      Add the following code to it:

      flask_app/templates/message.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Messages {% endblock %}</h1>
          <h2>{{ message }}</h2>
      {% endblock %}
      

      Save and close the file.

      In the code above, you extend the base template and override the content block. You add a title (Messages) in an H1 heading, and display the value of the message variable in an H2 heading.

      With the development server running, visit the following URLs on your browser:

      http://127.0.0.1:5000/messages/0
      http://127.0.0.1:5000/messages/1
      http://127.0.0.1:5000/messages/2
      http://127.0.0.1:5000/messages/3
      

      You’ll see that the H2 contains the text Message Zero, Message One, or Message Two respectively on each one of the first three URLs. However, on the fourth URL, the server will respond with an IndexError: list index out of range error message. In a production environment, the response would’ve been a 500 Internal Server Error, but the proper response here is a 404 Not Found to indicate that the server can’t find a message with an index of 3.

      You can respond with a 404 error using Flask’s abort() helper function. To do so, open the app.py file:

      Edit the first line to import the abort() function. Then edit the message() view function by adding a try ... except clause as shown in the highlighted parts below:

      flask_app/app.py

      from flask import Flask, render_template, abort
      
      # ...
      # ...
      
      
      @app.route('/messages/<int:idx>')
      def message(idx):
          messages = ['Message Zero', 'Message One', 'Message Two']
          try:
              return render_template('message.html', message=messages[idx])
          except IndexError:
              abort(404)
      

      Save and close the file.

      In the code above, you import the abort() function, which you use to abort the request and respond with an error. In the message() view function, you use a try ... except clause to wrap the function. You first try to return the messages template with the message that corresponds to the index in the URL. If the index has no corresponding message, the IndexError exception will be raised. You then use the except clause to catch that error, and you use abort(404) to abort the request and respond with a 404 Not Found HTTP error.

      With the development server running, use your browser to revisit the URL that responded with an IndexError earlier (or visit any URL with an index greater than 2):

      http://127.0.0.1:5000/messages/3
      

      You will see the following response:

      Not Found
      
      The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
      

      You now have a better error message that indicates that the server could not find the requested message.

      Next, you’ll make a template for the 404 error page and one for the 500 error page.

      First, you’ll register a function with the special @app.errorhandler() decorator as a handler for the 404 error. Open the app.py file for editing:

      nano app.py
      

      Edit the file by adding the highlighted part as follows:

      flask_app/app.py

      from flask import Flask, render_template, abort
      
      app = Flask(__name__)
      
      
      @app.errorhandler(404)
      def page_not_found(error):
          return render_template('404.html'), 404
      
      
      @app.route('/')
      def index():
          return render_template('index.html')
      
      
      @app.route('/messages/<int:idx>')
      def message(idx):
          messages = ['Message Zero', 'Message One', 'Message Two']
          try:
              return render_template('message.html', message=messages[idx])
          except IndexError:
              abort(404)
      

      Save and close the file.

      Here you use the @app.errorhandler() decorator to register the function page_not_found() as a custom error handler. The function takes the error as an argument, and it returns a call to the render_template() function with a template called 404.html. You will create this template later, and you can use another name if you want. You also return the integer 404 after the render_template() call. This tells Flask that the status code in the response should be 404. If you don’t add it, the default status code response will be 200, which means that the request has succeeded.

      Next, open a new 404.html template:

      Add the following code to it:

      flask_app/templates/404.html

      {% extends 'base.html' %}
      
      {% block content %}
              <h1>{% block title %} 404 Not Found. {% endblock %}</h1>
              <p>OOPS! Sammy couldn't find your page; looks like it doesn't exist.</p>
              <p>If you entered the URL manually, please check your spelling and try again.</p>
      {% endblock %}
      

      Save and close the file.

      Just like any other template, you extend the base template, you replace the content of the content and title blocks, and you add your own HTML code. Here you have an <h1> heading as the title, a <p> tag with a custom error message telling the user the page was not found, and a helpful message for users who might have entered the URL manually.

      You can use whatever HTML, CSS, and JavaScript you want in your error pages in the same way you would in other templates.

      With the development server running, use your browser to revisit the following URL:

      http://127.0.0.1:5000/messages/3
      

      You’ll see the page now has the navigation bar that’s in the base template and the custom error message.

      Similarly, you can add a custom error page for your 500 Internal Server Error errors. Open the app.py file:

      Add the following error handler below the 404 error handler:

      flask_app/app.py

      # ...
      
      @app.errorhandler(404)
      def page_not_found(error):
          return render_template('404.html'), 404
      
      
      @app.errorhandler(500)
      def internal_error(error):
          return render_template('500.html'), 500
      
      # ...
      

      Here you use the same pattern as you did for the 404 error handler. You use the app.errorhandler() decorator with a 500 argument to make a function called internal_error() into an error handler. You render a template called 500.html, and respond with a status code of 500.

      Then to demonstrate how the custom error will be presented, add a route that responds with a 500 HTTP error at the end of the file. This route will always give a 500 Internal Server Error regardless of whether the debugger is running or not:

      flask_app/app.py

      
      # ...
      @app.route('/500')
      def error500():
          abort(500)
      

      Here you make a route /500 and use the abort() function to respond with a 500 HTTP error.

      Save and close the file.

      Next, open the new 500.html template:

      Add the following code to it:

      flask_app/templates/500.html

      {% extends 'base.html' %}
      
      {% block content %}
              <h1>{% block title %} 500 Internal Server Error {% endblock %}</h1>
              <p>OOOOPS! Something went wrong on the server.</p>
              <p>Sammy is currently working on this issue. Please try again later.</p>
      {% endblock %}
      

      Save and close the file.

      Here, you do the same thing you did with the 404.html template. You extend the base template, and replace the content block with a title and two custom messages informing the user about the internal server error.

      With the development server running, visit the route that responds with a 500 error:

      http://127.0.0.1:5000/500
      

      Your custom page will appear instead of the generic error page.

      You now know how to use custom error pages for HTTP errors in your Flask application. Next, you’ll learn how to use logging to track events in your application. Tracking events helps you understand how your code behaves, which in turn helps with development and troubleshooting.

      Step 3 — Using Logging to Track Events in Your Application

      In this step, you will use logging to track events that happen when the server is running and the application is being used, which helps you see what is going on in your application code so you can troubleshoot errors easier.

      You have already seen logs whenever the development server is running, which typically look like this:

      127.0.0.1 - - [21/Sep/2021 14:36:45] "GET /messages/1 HTTP/1.1" 200 -
      127.0.0.1 - - [21/Sep/2021 14:36:52] "GET /messages/2 HTTP/1.1" 200 -
      127.0.0.1 - - [21/Sep/2021 14:36:54] "GET /messages/3 HTTP/1.1" 404 -
      

      In these logs, you can see the following information:

      • 127.0.0.1: The host the server was running on.
      • [21/Sep/2021 14:36:45]: The date and time of the request.
      • GET: The HTTP request method. In this case, GET is used to retrieve data.
      • /messages/2: The path the user requested.
      • HTTP/1.1: The HTTP version.
      • 200 or 404: The status code of the response.

      These logs help you diagnose problems that occur in your application. You can log more information when you want to know more details about certain requests using the logger app.logger Flask provides.

      With logging, you can use different functions to report information on different logging levels. Each level indicates an event happened with a certain degree of severity. The following functions can be used:

      • app.logger.debug(): For detailed information about the event.
      • app.logger.info(): Confirmation that things are working as expected.
      • app.logger.warning(): Indication that something unexpected happened (such as “disk space low”), but the application is working as expected.
      • app.logger.error(): An error occurred in some part of the application.
      • app.logger.critical(): A critical error; the entire application might stop working.

      To demonstrate how to use the Flask logger, open your app.py file for editing to log a few events:

      Edit the message() view function to look as follows:

      flask_app/app.py

      
      # ...
      
      @app.route('/messages/<int:idx>')
      def message(idx):
          app.logger.info('Building the messages list...')
          messages = ['Message Zero', 'Message One', 'Message Two']
          try:
              app.logger.debug('Get message with index: {}'.format(idx))
              return render_template('message.html', message=messages[idx])
          except IndexError:
              app.logger.error('Index {} is causing an IndexError'.format(idx))
              abort(404)
      
      # ...
      

      Save and close the file.

      Here, you logged a few events on different levels. You use app.logger.info() to log an event that’s working as expected (which is an INFO level). You use app.logger.debug() for detailed information (DEBUG level), mentioning that the application is now getting a message with a specific index. Then you use app.logger.error() to log the fact that an IndexError exception has been raised with the specific index that caused the issue (ERROR level, because an error occurred).

      Visit the following URL:

      http://127.0.0.1:5000/messages/1
      

      You’ll see the following information in the terminal where your server is running:

      Output

      [2021-09-21 15:17:02,625] INFO in app: Building the messages list... [2021-09-21 15:17:02,626] DEBUG in app: Get message with index: 1 127.0.0.1 - - [21/Sep/2021 15:17:02] "GET /messages/1 HTTP/1.1" 200 -

      Here you see the INFO message app.logger.info() logs, and the DEBUG message with the index number that you logged using app.logger.debug().

      Now visit a URL for a message that does not exist:

      http://127.0.0.1:5000/messages/3
      

      You’ll see the following information in the terminal:

      Output

      [2021-09-21 15:33:43,899] INFO in app: Building the messages list... [2021-09-21 15:33:43,899] DEBUG in app: Get message with index: 3 [2021-09-21 15:33:43,900] ERROR in app: Index 3 is causing an IndexError 127.0.0.1 - - [21/Sep/2021 15:33:43] "GET /messages/3 HTTP/1.1" 404 -

      As you can see, you have INFO and DEBUG logs that you’ve seen before, and a new ERROR log because a message with an index of 3 does not exist.

      Logging events, detailed information, and errors helps you identify where something went wrong and makes troubleshooting easier.

      You’ve learned in this step how to use the Flask logger. Check out How To Use Logging in Python 3 for a better understanding of logging. For an in-depth look at logging, see the Flask logging documentation and the Python documentation for logging.

      Conclusion

      You now know how to use debug mode in Flask, and how to troubleshoot and fix some common errors you may encounter when developing a Flask web application. You’ve also created custom error pages for common HTTP errors, and you’ve used the Flask logger to track events in your application to help you inspect and figure out how your application behaves.

      If you would like to read more about Flask, check out the Flask topic page.



      Source link

      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