13 - Effect & Ref

Just like the useState API, React has a number of other APIs (also called hooks). Two of those APIs/hooks are relevant for the file.io app we're going to migrate in the next part:

  • useEffect: Widely used to make API calls, but can also be used to perform any side effects like manually updating DOM, reading local storage, etc. For our app, we'll be using useEffect for making API calls.

  • useRef: A React Hook that lets you reference a value that’s not needed for rendering. For our app, we'll be using useRef for manipulating DOM (simulating clicks).

useEffect

The useEffect hook is a React Hook that lets you synchronize a component with an external system. Primarily, useEffect gets used to make API calls. Additionally, useEffect can also be used to execute some code only at initial rendering.

The useEffect API takes one mandatory and one optional input:

  • Function to execute: This is the mandatory input. This specifies the task for the useEffect.

  • Dependencies: This specifies when should the function present in useEffect execute. Should it execute every time the component is rendered? Should it execute when something changes (like state)?

Let's say you open a React based page. The following are the rough steps that get executed:

  • The component with empty/default data will be rendered

  • The useEffect hook will run an API call to some external system

  • The returned data will be used to update the state

  • The state update will cause a component re-rendering

Here is the flow visually:

Do you see any infinite loop here? The component rendering executes effect. The effect updates state. The new state triggers re-rendering. The re-rendering executes effect. And, so on.

This is the reason why execution of the useEffect hook needs to be controlled either to run exactly once at initial rendering or run when something changes in the component.

The standard way to run effect exactly once is by providing an empty dependency array as the second argument ([]). This'll ensure that React runs the effect exactly once.

Let's take an example to understand the useEffect hook better.

Problem

Write an app that shows a random joke on each page refresh.

Solution

This is a great candidate for effect. We want to fetch a new joke on each page refresh. A JSX will be defined that uses state. An effect will be provisioned to run exactly once. The effect will update the state. This'll cause component re-rendering which will show the joke on the page. The effect will not execute on re-rendering because of the empty dependency list ([]).

IMPORTANT NOTE:

With React v18, the effect function runs twice if strict mode is used in the development mode. As we're using development mode for this work, we've to remove the strict mode from index.js:

// Old code

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// New code

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <App />
);

Coming back to the joke problem, the fetched joke has two parts: setup and punchline. We can use a JS object to save these in a single state variable.

import React from "react";
import "./App.css";

function App() {
  const [joke, setJoke] = React.useState({});

  React.useEffect(() => {
    fetch("https://official-joke-api.appspot.com/random_joke")
      .then((resp) => resp.json())
      .then((data) => setJoke(data));
  }, []);

  return (
    <div className="App">
      <h1>{joke.setup}</h1>
      <h3>{joke.punchline}</h3>
    </div>
  );
}

export default App;

The same code with some helpful annotations is:

Before closing on the effect hook, let's solve one more problem that requires running useEffect on a dropdown selection (another very common use case).

Problem

Write an app that shows cryptocurrency price for a given currency. The list of currencies can be provisioned as a static data. Use bitcoin as the default currency.

Solution

In this app, the effect needs to be called two times:

  • At page refresh, show the current price for the default currency (Bitcoin)

  • At dropdown selection, show the price for chosen currency

We can use two effects for this:

  • Effect One: Called when the page loads, where the effect sets the dropdown to bitcoin. The dependency list will be [] to make this run only once.

  • Effect Two: Called when dropdown selection change. This one makes an external API call. The dependency list will be the state so that this effect runs on every state change.

A static list of supported currencies is maintained in the component. On selecting an option, the state gets updated which will trigger the effect, which will update the price in the state. The effect is configured to run only when id & name inside the state changes. The effect will not run when the price inside the state changes. This provides a granular control over effect execution.

The code of the app is:

import React from "react";
import "./App.css";

function App() {
  const currencies = [
    { id: 90, name: "Bitcoin" },
    { id: 80, name: "Ehtereum" },
    { id: 58, name: "XRP" },
    { id: 2, name: "Dogecoin" },
    { id: 1, name: "Litecoin" },
    { id: 2321, name: "Bitcoin Cash" },
  ];
  const [currency, setCurrency] = React.useState({
    name: "",
    id: 0,
    price: 0,
  });

  React.useEffect(() => setCurrency({ ...currencies[0] }), []);

  React.useEffect(() => {
    fetch(`https://api.coinlore.net/api/ticker/?id=${currency.id}`)
      .then((resp) => resp.json())
      .then((data) =>
        setCurrency((prev) => {
          return { ...prev, price: data[0].price_usd };
        })
      )
      .catch(() => {});
  }, [currency.name, currency.id]);

  function updateCurrency(event) {
    const currencyName = event.target.options[event.target.selectedIndex].text;
    setCurrency({ name: currencyName, id: event.target.value, price: 0 });
  }

  return (
    <div className="App">
      <select onChange={updateCurrency} className="currency-list">
        {currencies.map((v, index) => (
          <option key={index} value={v.id}>{v.name}</option>
        ))}
      </select>
      <br />
      <h1>{currency.name}</h1>
      {currency.price !== 0 && <h3>{currency.price} USD</h3>}
    </div>
  );
}

export default App;

Take some time and go through the code in detail. The key is that the first effect is dependent on nothing, so it runs exactly once. This effect takes the first currency out of the list of supported currencies and updates the state. The state change triggers the second effect because the second effect is dependent on changes to name & id in the state. The second effect makes an API call and updates the price in the state. This triggers a component rendering, but the second effect doesn't run again, as it depends only on id & name changes. A diagram might help understand it better:

Let's test it out:

That's all about the effect hook. This much knowledge of this hook is enough to write the file.io application. And, this much idea about effect is also enough for most of the day-to-day work.

useRef

The useRef hook allows you to persist values between renders. It can be used to store a mutable value that does not cause a re-render when updated. It can be used to access a DOM element directly. The useRef hook has a number of advanced use cases. For the file.io app, we'll be using useRef to simulate clicks. The question would be why we'd like to manipulate DOM directly?

Let's take a relevant example of an input field of type file that allows a file to get uploaded. The default appearance of such an input field is something like this:

import React from "react";
import "./App.css";

function App() {

  return (
    <div className="App">
      <input type="file" className="file"></input>
    </div>
  );
}

export default App;

The file upload field is not totally customizable. There is no way to use a regular button instead of the default appearance. This is a well known problem. If we want to use a regular button instead of the default file field, we need to do the following:

  • Create a hidden file upload button (<input type="file">)

  • Create a regular styled button (<buton>)

  • When regular button is clicked, simulate hidden file upload button click

In the vanilla JS world, it's something like this:

// HTML 

<input id="fileUpload" onclick="uploadFile()" type="file" hidden></input>
<button onclick="uploadFileTmp()">Upload file(s)</button>

// JS
function uploadFileTmp() {
  document.getElementById("#fileUpload").click();
}

This can be achieved in React world through the ref hook. The ref hook creates a reference that can be attached to any DOM element. Whenever needed, the DOM element can be manipulated using the reference.

The above code in the React world is:

import React from "react";
import "./App.css";

function App() {
  const fileRef = React.useRef();

  function handleUploadClick() {
    fileRef.current.click();
  }

  return (
    <div className="App">
      <input type="file" id="fileUpload" ref={fileRef} hidden></input>
      <button className="file" onClick={handleUploadClick}>
        Upload file(s)
      </button>
    </div>
  );
}

export default App;

The fileRef is attached to the hidden element. The visible button click handler clicks the hidden element. The reference has a current field which always points to a target.

--

That's all about the relevant hooks for the file.io app. So far, we'e decomposed file.io into React components, imported additional dependencies, and prepared client routing. We've also gone through all the required React APIs/hooks. It's time to write the code for upload and download functionality. The next section is all about rewriting file.io in React.

Last updated