React | NextJS

11.2 Refs, Axios, Fetch, and Spinners

Module Still Under Development

# Refs

One of the guiding principles of working in React is that we do not make direct changes to the DOM. We let the React rendering engine handle all the updates to the DOM.

For those rare times when you really need to have reference to a DOM element, React has Refs. The old version of React uses createRef to create a reference object.

The latest versions of React have a useRef hook. The newer version of Refs that use the hook let us create references for any mutatable object, not just DOM elements.

When you create a Ref you are creating an Object that will be maintained for the life of the component. This means even when state and prop values change and React updates the DOM, as long as the component has not been removed from the page, the reference will maintain its value. Remember that Objects in JavaScript exist and are passed around as references. We call the useRef method to create our reference object and pass it an initial value.

The reference object it creates will have a current property that will hold the value of the reference.

const myRef = useRef(123);
//this is a basic reference object
console.log(myRef.current);
1
2
3

A more common use for a reference it to hold the DOM element reference. To do this, we create a reference object with a null value. Then we use a ref attribute inside the JSX element that we want to track.

export default function MyComponent() {
  const myElem = useRef(null);

  function doFocus(ev) {
    //call this function when button is clicked
    //use the reference myElem
    //its .current property references the input element
    myElem.current.focus();
  }

  return (
    <>
      <input ref={myElem} type="text" />
      <button onClick={doFocus}>Focus the input</button>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Basic Fetch

Keep in mind that everything that we are doing in React is still just JavaScript. Any code that you wrote in first year can still be used in your React app. We are simply using the React framework, which is really just a whole bunch of functions and objects that have been written for us to save time.

If you want to make a fetch call, you can do it still. Put it in a function and call your function from a JSX Synthetic event.

export default function Parent(props) {
  const [data, setData] = useState([]);

  function getData() {
    fetch(url)
      .then((resp) => {
        if (!resp.ok) throw new Error(`Failed to fetch. HTTP Status code ${resp.status}.`);
        return resp.json();
      })
      .then(setData)
      .catch(console.warn)
      .finally(() => {
        //runs after success or failure
        //clear spinners and loaders here
      });
    //simplified function without proper error handling
  }

  return <button onClick={getData}>Click to Fetch Data</button>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

In the example above, we are using React to create a state variable, to turn JSX into DOM, and to connect the Synthetic onClick to an actual DOM click event. But it is all still just JavaScript.

# Axios

Axios is a library that can be used to make fetch calls. It was developed before fetch was standard and has a syntax that is similar but a bit simpler than fetch. It provides a standard way to handle failures etc.

Axios is an alternative to using fetch. It is up to you whether or not you use it. One advantage that Axios has, beyond the simplified syntax, is the ability to cancel / abort a fetch call. If you are building an app that makes many repeated API calls to the same end point, and you would like to abort older calls in place of new ones, then Axios would be a good library to include in your project.

Here is the website for Axios (opens new window)

You will need to use npm to install axios before you use it.

npm install axios
1

Axios has been used with NodeJS server-side apps for many years. This was because NodeJS had no built-in fetch method. The two main options were node-fetch and axios. For this reason, the examples in the website use the older require() syntax.

For our app, which runs in the browser, we will be using the import syntax.

import axios from 'axios';
1

Axios has a series of methods that match the HTTP methods - get, post, delete, etc.

axios
  .get(url)
  .then(function (response) {
    // handle success
    console.log(response);
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
  .then(function () {
    // always executed follow up
    // this would be where you dispose of your spinners
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Spinners & Loaders

A spinner or loader is a UI element that you display on screen to distract users from the fact that they have to wait for some content. It lets them know that the app has not failed but rather it is busy doing something for them.

Spinners are a critical part of good UX.

You can make a spinner out of any HTML or SVG that you want. CSS is a good choice to animate it.

At the most basic, you can have a text element that says Loading and add a CSS looped animation that just cycles the opacity between 10% and 100%.

Here is a short tutorial to give you some inspiration for building your own spinners.

# Hooks with Fetching

As was already discussed in the previous module on hooks, we can use the useEffect hook to call on any asynchronous function that we need. fetch is a perfect example.

The elements for any fetch & display component in React are:

  1. Create a state variable to hold the results array with useState, with a default value of null.
  2. Create a boolean state variable with useState to use in monitoring if a fetch is happening.
  3. In your Component return check the boolean state variable to know if you are still fetching and need to display a spinner.
  4. In your Component return check if the results array state variable is null, [], or has an array length > 0.
  5. If the results array is null, show nothing in the list. null means no fetch yet.
  6. If the results array is an empty array, show a default message in the list about no content.
  7. If the results array has a length > 0, use results.map() to build your list output.
  8. Add a useEffect function to initiate the fetch.
  9. Add an empty array as the second parameter for useEffect to be sure that it only runs on initial render.

Here is a basic example of all those elements, without using Axios or a real url.

import { useState, useEffect } from 'react';
import ListItem from './ListItem.js'; //the React Element to display each data item
import Spinner from './Spinner.js'; //a spinner for my app
import MessageBox, { displayError } from './MessageBox.js';

export default function List() {
  const [results, setResults] = useState(null);
  const [isFetching, setIsFetching] = useState(true); //so it shows the spinner as it renders

  useEffect(() => {
    fetch(url)
      .then((resp) => resp.json())
      .then((data) => {
        //stop the spinner and show the data
        setIsFetching(false);
        setResults(data); //assuming data is an array
      })
      .catch((err) => {
        displayError(err);
        setIsFetching(false);
      });
  }, []);

  return (
    <>
      {isFetching && <Spinner />}
      {results && <ul>{results.length === 0 ? <ListItem txt="No results" /> : results.map((item) => <ListItem key={item.id} txt={item.name} />)}</ul>}
    </>
  );
}

//the ListItem would be as follows
function ListItem({ txt }) {
  return <li>{txt}</li>;
}

//Sample Spinner with CSS that animates it
function Spinner() {
  return <h1 className="pulse">Loading...</h1>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

The <Spinner> is only displayed on screen when isFetching is true. When the Spinner disappears, the <ul> is added. As long as there are items in the results Array then the <ListItem>s are generated. The name property is extracted from each object in the results Array and passed to <ListItem> through props.

# Lazy Loading and Code Splitting

Lazy Loading is the ability to wait until a component is needed before loading its modules and imports. Imagine that you build a web app with React that was built from 40 different components. 20 of them get viewed all the time and the other 20 are only used infrequently.

Instead of loading all of those components and modules into memory, along with all their data, why not wait until the user clicks on a Link to actually load any of them.

To accomplish this we use a method called React.lazy() as part of the import. The lazy method accepts another function that does the import.

//instead of the standard
import Thing from './Thing.js';

//we create a variable and assign it the result of a function
// which does the dynamic import
const Thing = React.lazy(() => import('./Thing.js'));
1
2
3
4
5
6

This uses the JavaScript dynamic import method.

In conjunction with the lazy import we need to have a fallback for the component. We need something to show WHILE the dynamic import is happening. The fallback is creating by using the React <Suspense> component.

To use the <Suspense> component, we need to import it first. Then it will wrap the component being lazy loaded.

import React, { Suspense } from 'react';

const Things = React.lazy(() => import('./Things.js'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Things />
      </Suspense>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# What to do this week

TODO Things to do before next week.

Last Updated: 5/8/2024, 9:34:41 PM