One place for hosting & domains

      Performance

      How To Improve Website Performance Using gzip and Nginx on Ubuntu 20.04


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      A website’s performance depends partially on the size of all the files that a user’s browser must download. Reducing the size of those transmitted files can make your website faster. It can also make your website cheaper for those who pay for their bandwidth usage on metered connections.

      gzip is a popular data compression program. You can configure Nginx to use gzip to compress the files it serves on the fly. Those files are then decompressed by the browsers that support it upon retrieval with no loss whatsoever, but with the benefit of a smaller amount of data to transfer between the web server and browser. The good news is that compression support is ubiquitous among all major browsers, and there is no reason not to use it.

      Because of the way compression works in general and how gzip works, certain files compress better than others. For example, text files compress very well, often ending up over two times smaller. On the other hand, images such as JPEG or PNG files are already compressed by their nature, and second compression using gzip yields little or no results. Compressing files use up server resources, so it is best to compress only files that will benefit from the size reduction.

      In this tutorial, you will configure Nginx to use gzip compression. This will reduce the size of content sent to your website’s visitors and improve performance.

      Prerequisites

      To follow this tutorial, you will need:

      Step 1 — Creating Test Files

      In this step, we will create several test files in the default Nginx directory. We’ll use these files later to check Nginx’s default behavior for gzip’s compression and test that the configuration changes have the intended effect.

      To infer what kind of file is served over the network, Nginx does not analyze the file contents; that would be prohibitively slow. Instead, it looks up the file extension to determine the file’s MIME type, which denotes its purpose.

      Because of this behavior, the content of our test files is irrelevant. By naming the files appropriately, we can trick Nginx into thinking that, for example, one entirely empty file is an image and another is a stylesheet.

      Create a file named test.html in the default Nginx directory using truncate. This extension denotes that it’s an HTML page:

      • sudo truncate -s 1k /var/www/html/test.html

      Let’s create a few more test files in the same manner: one jpg image file, one css stylesheet, and one js JavaScript file:

      • sudo truncate -s 1k /var/www/html/test.jpg
      • sudo truncate -s 1k /var/www/html/test.css
      • sudo truncate -s 1k /var/www/html/test.js

      The next step is to check how Nginx behaves with respect to compressing requested files on a fresh installation with the files we have just created.

      Step 2 — Checking the Default Behavior

      Let’s check if the HTML file named test.html is served with compression. The command requests a file from our Nginx server and specifies that it is fine to serve gzip compressed content by using an HTTP header (Accept-Encoding: gzip):

      • curl -H "Accept-Encoding: gzip" -I http://localhost/test.html

      In response, you should see several HTTP response headers:

      Output

      HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 09 Feb 2021 19:04:25 GMT Content-Type: text/html Last-Modified: Tue, 09 Feb 2021 19:03:41 GMT Connection: keep-alive ETag: W/"6022dc8d-400" Content-Encoding: gzip

      In the last line, you can see the Content-Encoding: gzip header. This tells us that gzip compression was used to send this file. That’s because Nginx has gzip compression enabled automatically even on the fresh Ubuntu 20.04 installation.

      However, by default, Nginx compresses only HTML files. Every other file will be served uncompressed, which is less than optimal. To verify that, you can request our test image named test.jpg in the same way:

      • curl -H "Accept-Encoding: gzip" -I http://localhost/test.jpg

      The result should be slightly different than before:

      Output

      HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 09 Feb 2021 19:05:49 GMT Content-Type: image/jpeg Content-Length: 1024 Last-Modified: Tue, 09 Feb 2021 19:03:45 GMT Connection: keep-alive ETag: "6022dc91-400" Accept-Ranges: bytes

      There is no Content-Encoding: gzip header in the output, which means the file was served without any compression.

      You can repeat the test with the test CSS stylesheet:

      • curl -H "Accept-Encoding: gzip" -I http://localhost/test.css

      Once again, there is no mention of compression in the output:

      Output

      HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 09 Feb 2021 19:06:04 GMT Content-Type: text/css Content-Length: 1024 Last-Modified: Tue, 09 Feb 2021 19:03:45 GMT Connection: keep-alive ETag: "6022dc91-400" Accept-Ranges: bytes

      In the next step, we’ll tell Nginx to compress all sorts of files that will benefit from using gzip.

      Step 3 — Configuring Nginx’s gzip Settings

      To change the Nginx gzip configuration, open the main Nginx configuration file in nano or your favorite text editor:

      • sudo nano /etc/nginx/nginx.conf

      Find the gzip settings section, which looks like this:

      /etc/nginx/nginx.conf

      . . .
      ##
      # `gzip` Settings
      #
      #
      gzip on;
      gzip_disable "msie6";
      
      # gzip_vary on;
      # gzip_proxied any;
      # gzip_comp_level 6;
      # gzip_buffers 16 8k;
      # gzip_http_version 1.1;
      # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
      . . .
      

      You can see that gzip compression is indeed enabled by the gzip on directive, but several additional settings are commented out with # sign and have no effect. We’ll make several changes to this section:

      • Enable the additional settings by uncommenting all of the commented lines (i.e., by deleting the # at the beginning of the line)
      • Add the gzip_min_length 256; directive, which tells Nginx not to compress files smaller than 256 bytes. Very small files barely benefit from compression.
      • Append the gzip_types directive with additional file types denoting web fonts, icons, XML feeds, JSON structured data, and SVG images.

      After these changes have been applied, the settings section should look like this:

      /etc/nginx/nginx.conf

      . . .
      ##
      # `gzip` Settings
      #
      #
      gzip on;
      gzip_disable "msie6";
      
      gzip_vary on;
      gzip_proxied any;
      gzip_comp_level 6;
      gzip_buffers 16 8k;
      gzip_http_version 1.1;
      gzip_min_length 256;
      gzip_types
        application/atom+xml
        application/geo+json
        application/javascript
        application/x-javascript
        application/json
        application/ld+json
        application/manifest+json
        application/rdf+xml
        application/rss+xml
        application/xhtml+xml
        application/xml
        font/eot
        font/otf
        font/ttf
        image/svg+xml
        text/css
        text/javascript
        text/plain
        text/xml;
      . . .
      

      Save and close the file to exit.

      To enable the new configuration, restart Nginx:

      • sudo systemctl restart nginx

      Next, let’s make sure our new configuration works.

      Step 4 — Verifying the New Configuration

      Execute the same request as before for the test HTML file:

      • curl -H "Accept-Encoding: gzip" -I http://localhost/test.html

      The response will stay the same since compression has already been enabled for that filetype:

      Output

      HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 09 Feb 2021 19:04:25 GMT Content-Type: text/html Last-Modified: Tue, 09 Feb 2021 19:03:41 GMT Connection: keep-alive ETag: W/"6022dc8d-400" Content-Encoding: gzip

      However, if we request the previously uncompressed CSS stylesheet, the response will be different:

      • curl -H "Accept-Encoding: gzip" -I http://localhost/test.css

      Now gzip is compressing the file:

      Output

      HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 09 Feb 2021 19:21:54 GMT Content-Type: text/css Last-Modified: Tue, 09 Feb 2021 19:03:45 GMT Connection: keep-alive Vary: Accept-Encoding ETag: W/"6022dc91-400" Content-Encoding: gzip

      From all test files created in step 1, only the test.jpg image file should stay uncompressed. We can test this the same way:

      • curl -H "Accept-Encoding: gzip" -I http://localhost/test.jpg

      There is no gzip compression:

      Output

      HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 09 Feb 2021 19:25:40 GMT Content-Type: image/jpeg Content-Length: 1024 Last-Modified: Tue, 09 Feb 2021 19:03:45 GMT Connection: keep-alive ETag: "6022dc91-400" Accept-Ranges: bytes

      Here the Content-Encoding: gzip header is not present in the output as expected.

      If that is the case, you have configured gzip compression in Nginx successfully.

      Conclusion

      Changing Nginx configuration to utilize gzip compression is easy, but the benefits can be immense. Not only will visitors with limited bandwidth receive the site faster, but all other users will also see noticeable speed gains. Search engines will be happy about the site loading quicker too. Loading speed is now a crucial metric in how the search engines rank websites, and using gzip is one big step to improve it.



      Source link

      How To Avoid Performance Pitfalls in React with memo, useMemo, and useCallback


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

      Introduction

      In React applications, performance problems can come from network latency, overworked APIs, inefficient third-party libraries, and even well-structured code that works fine until it encounters an unusually large load. Identifying the root cause of performance problems can be difficult, but many of these problems originate from component re-rendering. Either the component re-renders more than expected or the component has a data-heavy operation that can cause each render to be slow. Because of this, learning how to prevent unneeded re-renders can help to optimize the performance of your React application and create a better experience for your user.

      In this tutorial, you’ll focus on optimizing performance in React components. To explore the problem, you’ll build a component to analyze a block of text. You’ll look at how different actions can trigger re-renders and how you can use Hooks and memoization to minimize expensive data calculations. By the end of this tutorial, you’ll be familiar with many performance enhancing Hooks, such as the useMemo and useCallback Hook, and the circumstances that will require them.

      Prerequisites

      • You will need a development environment running Node.js; this tutorial was tested on Node.js version 10.22.0 and npm version 6.14.6. To install this on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.

      • A React development environment set up with Create React App, with the non-essential boilerplate removed. To set this up, follow Step 1 — Creating an Empty Project of the How To Manage State on React Class Components tutorial. This tutorial will use performance-tutorial as the project name.

      • If you are new to debugging in React, check out the tutorial How To Debug React Components Using React Developer Tools, and familiarize yourself with the developer tools in the browser you are using, such as Chrome DevTools and Firefox Developer Tools.

      • You will also need a basic knowledge of JavaScript, HTML, and CSS, which you can find in our How To Build a Website With HTML series, How To Build a Website With CSS series, and in How To Code in JavaScript.

      Step 1 — Preventing Re-renders with memo

      In this step, you’ll build a text analyzing component. You’ll create an input to take a block of text and a component that will calculate the frequency of letters and symbols. You’ll then create a scenario where the text analyzer performs poorly and you’ll identify the root cause of the performance problem. Finally, you’ll use the React memo function to prevent re-renders on the component when a parent changes, but the props to the child component do not change.

      By the end of this step, you’ll have a working component that you’ll use throughout the rest of the tutorial and an understanding of how parent re-rendering can create performance problems in child components.

      Building a Text Analyzer

      To begin, add a <textarea> element to App.js.

      Open App.js in a text editor of your choice:

      • nano src/components/App/App.js

      Then add a <textarea>input with a <label>. Place the label inside a <div> with a className of wrapper by adding the following highlighted code:

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

      import React from 'react';
      import './App.css';
      
      function App() {
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Add Your Text Here:</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
              >
              </textarea>
            </label>
          </div>
        )
      }
      
      export default App;
      

      This will create an input box for the sample application. Save and close the file.

      Open App.css to add some styles:

      • nano src/components/App/App.css

      Inside App.css, add padding to the .wrapper class, then add a margin to the div elements. Replace the CSS with the following:

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

      
      .wrapper {
          padding: 20px;
      }
      
      .wrapper div {
          margin: 20px 0;
      }
      

      This will add separation between the input and the data display. Save and close the file.

      Next, create a directory for the CharacterMap component. This component will analyze the text, calculate the frequency of each letter and symbol, and display the results.

      First, make the directory:

      • mkdir src/components/CharacterMap

      Then open CharacterMap.js in a text editor:

      • nano src/components/CharacterMap/CharacterMap.js

      Inside, create a component called CharacterMap that takes text as a prop and displays the text inside a <div>:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React from 'react';
      import PropTypes from 'prop-types';
      
      export default function CharacterMap({ text }) {
        return(
          <div>
            Character Map:
            {text}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        text: PropTypes.string.isRequired
      }
      

      Notice that you are adding a PropType for the text prop to introduce some weak typing.

      Add a function to loop over the text and extract the character information. Name the function itemize and pass the text as an argument. The itemize function is going to loop over every character several times and will be very slow as the text size increases. This will make it a good way to test performance:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        const letters = text.split('')
          .filter(l => l !== ' ')
          .reduce((collection, item) => {
            const letter = item.toLowerCase();
            return {
              ...collection,
              [letter]: (collection[letter] || 0) + 1
            }
          }, {})
        return letters;
      }
      
      export default function CharacterMap({ text }) {
        return(
          <div>
            Character Map:
            {text}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        text: PropTypes.string.isRequired
      }
      

      Inside itemize, you convert the text into an array by using .split on every character. Then you remove the spaces using the .filter method and use the .reduce method to iterate over each letter. Inside the .reduce method, use an object as the initial value, then normalize the character by converting it to lower case and adding 1 to the previous total or 0 if there was no previous total. Update the object with the new value while preserving previous values using the Object spread operator.

      Now that you have created an object with a count for each character, you need to sort it by the highest character. Convert the object to an array of pairs with Object.entries. The first item in the array is the character and the second item is the count. Use the .sort method to place the most common characters on top:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        const letters = text.split('')
          .filter(l => l !== ' ')
          .reduce((collection, item) => {
            const letter = item.toLowerCase();
            return {
              ...collection,
              [letter]: (collection[letter] || 0) + 1
            }
          }, {})
        return Object.entries(letters)
          .sort((a, b) => b[1] - a[1]);
      }
      
      export default function CharacterMap({ text }) {
        return(
          <div>
            Character Map:
            {text}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        text: PropTypes.string.isRequired
      }
      

      Finally, call the itemize function with the text and display the results:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      
      import React from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        const letters = text.split('')
          .filter(l => l !== ' ')
          .reduce((collection, item) => {
            const letter = item.toLowerCase();
            return {
              ...collection,
              [letter]: (collection[letter] || 0) + 1
            }
          }, {})
        return Object.entries(letters)
          .sort((a, b) => b[1] - a[1]);
      }
      
      export default function CharacterMap({ text }) {
        return(
          <div>
            Character Map:
            {itemize(text).map(character => (
              <div key={character[0]}>
                {character[0]}: {character[1]}
              </div>
            ))}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        text: PropTypes.string.isRequired
      }
      

      Save and close the file.

      Now import the component and render inside of App.js. Open App.js:

      • nano src/components/App/App.js

      Before you can use the component, you need a way to store the text. Import useState then call the function and store the values on a variable called text and an update function called setText.

      To update the text, add a function to onChange that will pass the event.target.value to the setText function:

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

      import React, { useState } from 'react';
      import './App.css';
      
      function App() {
        const [text, setText] = useState('');
      
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Your Text</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
                onChange={event => setText(event.target.value)}
              >
              </textarea>
            </label>
          </div>
        )
      }
      
      export default App;
      

      Notice that you are initializing useState with an empty string. This will ensure that the value you pass to the CharacterMap component is always a string even if the user has not yet entered text.

      Import CharacterMap and render it after the <label> element. Pass the text state to the text prop:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { useState } from 'react';
      import './App.css';
      
      import CharacterMap from '../CharacterMap/CharacterMap';
      
      function App() {
        const [text, setText] = useState('');
      
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Your Text</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
                onChange={event => setText(event.target.value)}
              >
              </textarea>
            </label>
            <CharacterMap text={text} />
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, the browser will refresh and when you add text, you’ll find the character analysis after the input:

      Input with results below

      As shown in the example, the component performs fairly well with a small amount of text. With every keystroke, React will update the CharacterMap with new data. But performance locally can be misleading. Not all devices will have the same memory as your development environment.

      Testing Performance

      There are multiple ways to test performance of your application. You can add large volumes of text or you can set your browser to use less memory. To push the component to a performance bottleneck, copy the Wikipedia entry for GNU and paste it in the text box. Your sample may be slightly different depending on how the Wikipedia page is edited.

      After pasting the entry into your text box, try typing the additional letter e and notice how long it takes to display. There will be a significant pause before the character map updates:

      Animation showing the delay when typing

      If the component is not slow enough and you are using Firefox, Edge, or some other browser, add more text until you notice a slowdown.

      If you are using Chrome, you can throttle the CPU inside the performance tab. This is a great way to emulate a smartphone or an older piece of hardware. For more information, check out the Chrome DevTools documentation.

      Performance Throttling in Chrome DevTools

      If the component is too slow with the Wikipedia entry, remove some text. You want to receive a noticable delay, but you do not want to make it unusably slow or to crash your browser.

      Preventing Re-Rendering of Child Components

      The itemize function is the root of the delay identified in the last section. The function does a lot of work on each entry by looping over the contents several times. There are optimizations you can perform directly in the function itself, but the focus of this tutorial is how to handle component re-rendering when the text does not change. In other words, you will treat the itemize function as a function that you do not have access to change. The goal will be to run it only when necessary. This will show how to handle performance for APIs or third-party libraries that you can’t control.

      To start, you will explore a situation where the parent changes, but the child component does not change.

      Inside of App.js, add a paragraph explaining how the component works and a button to toggle the information:

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

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      import CharacterMap from '../CharacterMap/CharacterMap';
      
      function App() {
        const [text, setText] = useState('');
        const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
      
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Your Text</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
                onChange={event => setText(event.target.value)}
              >
              </textarea>
            </label>
            <div>
              <button onClick={toggleExplanation}>Show Explanation</button>
            </div>
            {showExplanation &&
              <p>
                This displays a list of the most common characters.
              </p>
            }
            <CharacterMap text={text} />
          </div>
        )
      }
      
      export default App;
      

      Call the useReducer Hook with a reducer function to reverse the current state. Save the output to showExplanation and toggleExplanation. After the <label>, add a button to toggle the explanation and a paragraph that will render when showExplanation is truthy.

      Save and exit the file. When the browser refreshes, click on the button to toggle the explanation. Notice how there is a delay.

      Delay when toggling the Explanation

      This presents a problem. Your users shouldn’t encounter a delay when they are toggling a small amount of JSX. The delay occurs because when the parent component changes—App.js in this situation—the CharacterMap component is re-rendering and re-calculating the character data. The text prop is identical, but the component still re-renders because React will re-render the entire component tree when a parent changes.

      If you profile the application with the browser’s developer tools, you’ll discover that the component re-renders because the parent changes. For a review of profiling using the developer tools, check out How To Debug React Components Using React Developer Tools.

      Parent component re-renders in browser developer tools

      Since CharacterMap contains an expensive function, it should only re-render it when the props change.

      Open CharacterMap.js:

      • nano src/components/CharacterMap/CharacterMap.js

      Next, import memo, then pass the component to memo and export the result as the default:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo } from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        ...
      }
      
      function CharacterMap({ text }) {
        return(
          <div>
            Character Map:
            {itemize(text).map(character => (
              <div key={character[0]}>
                {character[0]}: {character[1]}
              </div>
            ))}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        text: PropTypes.string.isRequired
      }
      
      export default memo(CharacterMap);
      

      Save the file. When you do, the browser will reload and there will no longer be a delay after you click the button before you get the result:

      No delay when toggling the explanation in the test app

      If you look at the developer tools, you’ll find that the component no longer re-renders:

      Component did not re-render

      The memo function will perform a shallow comparison of props and will re-render only when the props change. A shallow comparison will use the === operator to compare the previous prop to the current prop.

      It’s important to remember that the comparison is not free. There is a performance cost to check the props, but when you have a clear performance impact such as an expensive calculation, it is worth it to prevent re-renders. Further, since React performs a shallow comparison, the component will still re-render when a prop is an object or a function. You will read more about handling functions as props in Step 3.

      In this step, you created an application with a long, slow calculation. You learned how parent re-rendering will cause a child component to re-render and how to prevent the re-render using memo. In the next step, you’ll memoize actions in a component so that you only perform actions when specific properties change.

      Step 2 — Caching Expensive Data Calculations with useMemo

      In this step, you’ll store the results of slow data calculations with the useMemo Hook. You’ll then incorporate the useMemo Hook in an existing component and set conditions for data re-calculations. By the end of this step, you’ll be able to cache expensive functions so that they will only run when specific pieces of data change.

      In the previous step, the toggled explanation of the component was part of the parent. However, you could instead add it to the CharacterMap component itself. When you do, CharacterMap will have two properties, the text and showExplanation, and it will display the explanation when showExplanation is truthy.

      To start, open CharacterMap.js:

      • nano src/components/CharacterMap/CharacterMap.js

      Inside of CharacterMap, add a new property of showExplanation. Display the explanation text when the value of showExplanation is truthy:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo } from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        ...
      }
      
      function CharacterMap({ showExplanation, text }) {
        return(
          <div>
            {showExplanation &&
              <p>
                This display a list of the most common characters.
              </p>
            }
            Character Map:
            {itemize(text).map(character => (
              <div key={character[0]}>
                {character[0]}: {character[1]}
              </div>
            ))}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        showExplanation: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired
      }
      
      export default memo(CharacterMap);
      

      Save and close the file.

      Next, open App.js:

      • nano src/components/App/App.js

      Remove the paragraph of explanation and pass showExplanation as a prop to CharacterMap:

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

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      import CharacterMap from '../CharacterMap/CharacterMap';
      
      function App() {
        const [text, setText] = useState('');
        const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
      
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Your Text</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
                onChange={event => setText(event.target.value)}
              >
              </textarea>
            </label>
            <div>
              <button onClick={toggleExplanation}>Show Explanation</button>
            </div>
            <CharacterMap showExplanation={showExplanation} text={text} />
          </div>
        )
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will refresh. If you toggle the explanation, you will again receive the delay.

      Delay when toggling explanation

      If you look at the profiler, you’ll discover that the component re-rendered because the showExplanation prop changed:

      Re-render because prop changed

      The memo function will compare props and prevent re-renders if no props change, but in this case the showExplanation prop does change, so the whole component will re-render and the component will re-run the itemize function.

      In this case, you need to memoize specific parts of the component, not the whole component. React provides a special Hook called useMemo that you can use to preserve parts of your component across re-renders. The Hook takes two arguments. The first argument is a function that will return the value you want to memoize. The second argument is an array of dependencies. If a dependency changes, useMemo will re-run the function and return a value.

      To implement useMemo, first open CharacterMap.js:

      • nano src/components/CharacterMap/CharacterMap.js

      Declare a new variable called characters. Then call useMemo and pass an anonymous function that returns the value of itemize(text) as the first argument and an array containing text as the second argument. When useMemo runs, it will return the result of itemize(text) to the characters variable.

      Replace the call to itemize in the JSX with characters:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo, useMemo } from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        ...
      }
      
      function CharacterMap({ showExplanation, text }) {
        const characters = useMemo(() => itemize(text), [text]);
        return(
          <div>
            {showExplanation &&
              <p>
                This display a list of the most common characters.
              </p>
            }
            Character Map:
            {characters.map(character => (
              <div key={character[0]}>
                {character[0]}: {character[1]}
              </div>
            ))}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        showExplanation: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired
      }
      
      export default memo(CharacterMap);
      

      Save the file. When you do, the browser will reload and there will be no delay when you toggle the explanation.

      Animation showing no delay when toggling explanation

      If you profile the component, you will still find that it re-renders, but the time it takes to render will be much shorter. In this example it took .7 milliseconds compared to 916.4 milliseconds without the useMemo Hook. That’s because React is re-rendering the component, but it is not re-running the function contained in the useMemo Hook. You’re able to preserve the result while still allowing other parts of the component to update:

      Developer tools profile showing that the component renders in .7ms

      If you change the text in the textbox, there will still be a delay because the dependency—text—changed, so useMemo will re-run the function. If it did not re-run, you would have old data. The key point is that it only runs when the data it needs changes.

      In this step, you memoized parts of your component. You isolated an expensive function from the rest of the component and used the useMemo Hook to run the function only when certain dependencies change. In the next step, you’ll memoize functions to prevent shallow comparison re-renders.

      Step 3 — Managing Function Equality Checks with useCallback

      In this step, you’ll handle props that are difficult to compare in JavaScript. React uses strict equality checking when props change. This check determines when to re-run Hooks and when to re-render components. Since JavaScript functions and objects are difficult to compare, there are situations where a prop would be effectively the same, but still trigger a re-render.

      You can use the useCallback Hook to preserve a function across re-renders. This will prevent unnecessary re-renders when a parent component recreates a function. By the end of this step, you’ll be able to prevent re-renders using the useCallback Hook.

      As you build your CharacterMap component, you may have a situation where you need it to be more flexible. In the itemize function, you always convert the character to lower case, but some consumers of the component may not want that functionality. They may want to compare upper and lowercase characters or want to convert all characters to upper case.

      To facilitate this, add a new prop called transformer that will change the character. The transformer function will be anything that takes a character as an argument and returns a string of some sort.

      Inside of CharacterMap, add transformer as a prop. Give it a PropType of function with a default of null:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo, useMemo } from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text){
        const letters = text.split('')
          .filter(l => l !== ' ')
          .reduce((collection, item) => {
            const letter = item.toLowerCase();
            return {
              ...collection,
              [letter]: (collection[letter] || 0) + 1
            }
          }, {})
        return Object.entries(letters)
          .sort((a, b) => b[1] - a[1]);
      }
      
      function CharacterMap({ showExplanation, text, transformer }) {
        const characters = useMemo(() => itemize(text), [text]);
        return(
          <div>
            {showExplanation &&
              <p>
                This display a list of the most common characters.
              </p>
            }
            Character Map:
            {characters.map(character => (
              <div key={character[0]}>
                {character[0]}: {character[1]}
              </div>
            ))}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        showExplanation: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired,
        transformer: PropTypes.func
      }
      
      CharacterMap.defaultProps = {
        transformer: null
      }
      
      export default memo(CharacterMap);
      

      Next, update itemize to take transformer as an argument. Replace the .toLowerCase method with the transformer. If transformer is truthy, call the function with the item as an argument. Otherwise, return the item:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo, useMemo } from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text, transformer){
        const letters = text.split('')
          .filter(l => l !== ' ')
          .reduce((collection, item) => {
            const letter = transformer ? transformer(item) : item;
            return {
              ...collection,
              [letter]: (collection[letter] || 0) + 1
            }
          }, {})
        return Object.entries(letters)
          .sort((a, b) => b[1] - a[1]);
      }
      
      function CharacterMap({ showExplanation, text, transformer }) {
          ...
      }
      
      CharacterMap.propTypes = {
        showExplanation: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired,
        transformer: PropTypes.func
      }
      
      CharacterMap.defaultProps = {
        transformer: null
      }
      
      export default memo(CharacterMap);
      

      Finally, update the useMemo Hook. Add transformer as a dependency and pass it to the itemize function. You want to be sure that your dependencies are exhaustive. That means you need to add anything that might change as a dependency. If a user changes the transformer by toggling between different options, you’d need to re-run the function to get the correct value.

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo, useMemo } from 'react';
      import PropTypes from 'prop-types';
      
      function itemize(text, transformer){
        ...
      }
      
      function CharacterMap({ showExplanation, text, transformer }) {
        const characters = useMemo(() => itemize(text, transformer), [text, transformer]);
        return(
          <div>
            {showExplanation &&
              <p>
                This display a list of the most common characters.
              </p>
            }
            Character Map:
            {characters.map(character => (
              <div key={character[0]}>
                {character[0]}: {character[1]}
              </div>
            ))}
          </div>
        )
      }
      
      CharacterMap.propTypes = {
        showExplanation: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired,
        transformer: PropTypes.func
      }
      
      CharacterMap.defaultProps = {
        transformer: null
      }
      
      export default memo(CharacterMap);
      

      Save and close the file.

      In this application, you don’t want to give users the ability to toggle between different functions. But you do want the characters to be lower case. Define a transformer in App.js that will convert the character to lower case. This function will never change, but you do need to pass it to the CharacterMap.

      Open App.js:

      • nano src/components/App/App.js

      Then define a function called transformer that converts a character to lower case. Pass the function as a prop to CharacterMap:

      performance-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      import CharacterMap from '../CharacterMap/CharacterMap';
      
      function App() {
        const [text, setText] = useState('');
        const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
        const transformer = item => item.toLowerCase();
      
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Your Text</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
                onChange={event => setText(event.target.value)}
              >
              </textarea>
            </label>
            <div>
              <button onClick={toggleExplanation}>Show Explanation</button>
            </div>
            <CharacterMap showExplanation={showExplanation} text={text} transformer={transformer} />
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, you will find that the delay has returned when you toggle the explanation.

      Animation showing a delay when toggling explanation

      If you profile the component, you will find that the component re-renders because the props change and the Hooks changed:

      Profile for transformer

      If you look closely, you’ll find that the showExplanation prop changed, which makes sense because you clicked the button, but the transformer prop also changed.

      When you made a state change in App by clicking on the button, the App component re-rendered and redeclared the transformer. Even though the function is the same, it is not referentially identical to the previous function. That means it’s not strictly identical to the previous function.

      If you open the browser console and compared identical functions, you’d find that the comparison is false, as shown in the following code block:

      const a = () = {};
      const b = () = {};
      
      a === a
      // This will evaluate to true
      
      a === b
      // This will evaluate to false
      

      Using the === comparison operator, this code shows that two functions are not considered equal, even if they have the same values.

      To avoid this problem, React provides a Hook called useCallback. The Hook is similar to useMemo: it takes a function as the first argument and an array of dependencies as the second argument. The difference is that useCallback returns the function and not the result of the function. Like the useMemo Hook, it will not recreate the function unless a dependency changes. That means that the useMemo Hook in CharacterMap.js will compare the same value and the Hook will not re-run.

      Inside of App.js, import useCallback and pass the anonymous function as the first argument and an empty array as the second argument. You never want App to recreate this function:

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

      import React, { useCallback, useReducer, useState } from 'react';
      import './App.css';
      
      import CharacterMap from '../CharacterMap/CharacterMap';
      
      function App() {
        const [text, setText] = useState('');
        const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
        const transformer = useCallback(item => item.toLowerCase(), []);
      
        return(
          <div className="wrapper">
            <label htmlFor="text">
              <p>Your Text</p>
              <textarea
                id="text"
                name="text"
                rows="10"
                cols="100"
                onChange={event => setText(event.target.value)}
              >
              </textarea>
            </label>
            <div>
              <button onClick={toggleExplanation}>Show Explanation</button>
            </div>
            <CharacterMap showExplanation={showExplanation} text={text} transformer={transformer} />
          </div>
        )
      }
      
      export default App;
      

      Save and close the file. When you do, you’ll be able to toggle the explanation without re-running the function.

      Animation showing no delay when toggling the explanation component

      If you profile the component, you’ll find that the Hook no longer runs:

      Image of the browser developer tools showing that the Hook does not run

      In this particular component, you do not actually need the useCallback Hook. You could declare the function outside of the component and it would never re-render. You should only declare functions inside of your component if they require some sort of prop or stateful data. But there are times when you need to create functions based on internal state or props and in those situations you can use the useCallback Hook to minimize re-renders.

      In this step, you preserved functions across re-renders using the useCallback Hook. You also learned how those functions will retain equality when compared as props or dependencies in a Hook.

      Conclusion

      You now have the tools to improve performance on expensive components. You can use memo, useMemo, and useCallback to avoid costly component re-renders. But all these strategies include a performance cost of their own. memo will take extra work to compare properties, and the Hooks will need to run extra comparisons on each render. Only use these tools when there is a clear need in your project, otherwise you risk adding in your own latency.

      Finally, not all performance problems require a technical fix. There are times when the performance cost is unavoidable—such as slow APIs or large data conversions—and in those situations you can solve the problem using design by either rendering loading components, showing placeholders while asynchronous functions are running, or other enhancements to 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

      Networks and Online Gaming: 3 Ways to Improve Performance and Retain Your Audience


      What makes or breaks the technical success of a new multiplayer video game? Or for that matter, the success of any given online gaming session or match? There are a lot of reasons, to be sure, but success typically boils down to factors outside of the end users’ control. At the top of the list, arguably, is network performance.

      In June, 2018 Fornite experienced a network interruption that caused world-famous streamer, Ninja, to swap mid-stream to Hi-Rez’s Realm Royale. Ninja gave the game rave reviews, resulting in a huge userbase jumping over to play Realm Royale. And just this month, the launch of Wolcen: Lords of Mayhem was darkened by infrastructure issues as the servers couldn’t handle the number of users flocking to the game. While both popular games might not have experienced long-term damage, ongoing issues like these can turn users toward a competitor’s game or drive them away for good.

      Low latency is so vital, that in a 2019 survey, seven in 10 gamers said they will play a laggy game for less than 10 minutes before quitting. And nearly three in 10 say what matters most about an online game is having a seamless gaming experience without lag. What can game publishers do to prevent lag, increase network performance and increase the chances that their users won’t “rage quit”?

      Taking Control of the Network to Avoid Log Offs

      There are a few different ways to answer the question and avoid scenario outlined above, but some solutions are stronger than others.

      Increase Network Presence with Edge Deployments

      One option is to spread nodes across multiple geographical presences to reduce the distance a user must traverse to connect. Latency starts as a physics problem, so the shorter the distance between data centers and users, the lower the latency.

      This approach isn’t always the best answer, however, as everyday there can be both physical and logical network issues just miles apart from a user and a host. Some of these problems can be the difference between tens to thousands of milliseconds across a single carrier.

      Games are also increasingly global. You can put a server in Los Angeles to be close to users on the West Coast, but they’re going to want to play with their friends on the East Coast, or somewhere even further away.

      Connect Through the Same Carriers as the End Users

      Another answer is to purchase connectivity to some of the same networks end users will connect from, such as Comcast, AT&T, Time Warner, Telecom, Verizon, etc.

      A drawback of this option, though, stems from the abolishment of Net Neutrality. Carriers don’t necessarily need to honor best-route methodology anymore, meaning they can prioritize cost efficiency over performance on network configurations. I’ve personally observed traffic going from Miami to Tampa being routed all the way to Houston and back, as show in the images below.

      Network routing
      The traffic on the left follows best-route methodology, while the traffic on the right going from Miami to Tampa is being routed through Houston. This is one consequence of the abolishment of Net Neutrality.

      Purchasing connectivity that gets you directly into the homes of end-users may seem like the best method to reduce latency, but bottlenecks or indirect routing inside these large carriers’ networks can cause issues. A major metro market in the United States can also have three to four incumbent consumer carriers providing residential services to gamers, necessitating and IP blend to effectively reach end users. However, startups or gaming companies don’t want to build their own blended IP solution in every market they want to build out in.

      Choose a Host with a Blended Carrier Agreement

      The best possible solution to the initial scenario is to host with a carrier that has a blended carrier agreement, with a network route optimization technology to algorithmically traverse all of those carriers.

      Take for example, INAP’s Performance IP® solution. This technology makes a daily average of nearly 500 million optimizations across INAP’s global network to automatically put a customer’s outbound traffic on the best-performing route. This type of technology reduces latency upwards of 44 percent and prevents packet loss, preventing users from experiencing the lag that can change the fate of a game’s commercial success. You can explore our IP solution by running your own performance test.

      Taking Control When Uncontrollable Factors are at Play

      There will be times that game play is affected by end user hardware. It makes a difference, and it always will, but unfortunately publishers can’t control the type of access their users have to the internet. In some regions of the world, high speed internet is just a dream, while in others it would be unfathomable to go without high-speed internet access.

      Inline end user networking equipment can also play a role in network behavior. Modems, switches, routers and carrier equipment can cause poor performance. Connectivity being switched through an entire neighborhood, throughput issues during peak neighborhood activities, satellite dishes angled in an unoptimized position limiting throughput—there’s a myriad of reasons that user experience can be impacted.

      With these scenarios, end users often understand what they are working with and make mental allowances to cope with any limitations. Or they’ll upgrade their internet service and gaming hardware accordingly.

      The impact of network performance on streaming services and game play can’t be underscored enough. Most end users will make the corrections they can in order to optimize game play and connectivity. The rest is up to the publisher.

      Explore INAP’s Global Network.

      LEARN MORE

      Dan Lotterman


      READ MORE



      Source link