Learning React For Dummies: React Hooks pt. 1 (useState, useEffect)

Week Two

Bram Donovan's photo
Bram Donovan
·Jan 7, 2022·

6 min read

In react, it seems as thought most of the time it's a lot easier to just use function components instead of dealing with a full Component that has a constructor and all that. One way we make our function components powerful easily is to use something called Hooks. Hooks allow us to "hook" into React features like state.

Example:

Here is an example of a useState Hook.

import React, { useState } from "react";
import ReactDOM from "react-dom";

function FavoriteColor() {
  const [color, setColor] = useState("red");

  return (
    <>
      <h1>My favorite color is {color}!</h1>
      <button
        type="button"
        onClick={() => setColor("blue")}
      >Blue</button>
      <button
        type="button"
        onClick={() => setColor("red")}
      >Red</button>
      <button
        type="button"
        onClick={() => setColor("pink")}
      >Pink</button>
      <button
        type="button"
        onClick={() => setColor("green")}
      >Green</button>
    </>
  );
}

useState Hook

One of the most common hooks that'll we'll come across is the useState hook. As the name implies, useState allows us to track state in a function component.

Remember that state refers to data or properties that need to be tracked in an application.

Import useState

To get useState into our component, we need to import it onto the page.

import { useState } from 'react'

Note that we are destructuring useState from react because it is a named export.

Initialize useState

We can initialize our state by making a useState call in our function component.

useState accepts an initial state and returns two values:

  • The current state.
  • A function that updates the state

We typically name these something like [yourProp, setYourProp] = useState('')

Here we initialized a state of an empty string(('')) on yourProp.

import { useState } from 'react'

function FavoriteNumber() {
    const [number, setNumber] = useState(0)
}

Notice that again we are destructuring the returned values from useState.

The first value, number, is our current state.

The second value, setNumber, is the function that is used to update our state.

These names are variables that can be any name you like

Finally, we set the initial state to 0.

Read State

We can now include our state anywhere in the component.

import { useState } from 'react'

function FavoriteNumber() { 
     const [number, setNumber]= useState(0)

     return <h1>My favorite number is {number}!</h1>
}

Update State

If our favorite number isn't actually 0 and we want to update our state, we use our state updater function.

We should never directly update our state. Ex: number = 39 is not allowed.

Example:

We'll use a button to update the state.

import { useState } from 'react'

function FavoriteNumber() { 
     const [number, setNumber]= useState(0)

     return (
       <>
                <h1>My favorite number is {number}!</h1>
                <button 
                 type="button"
                 onClick={() => setNumber(3)}
                > 3 </button>
       </>

     )
}

What Can We Hold With State?

The useState Hook can be used to keep track of strings, numbers, booleans, arrays, objects, and any combination of these!

It's common to use multiple state hooks to track individual values.

import { useState } from "react";

function Car() {
  const [brand, setBrand] = useState("Ford");
  const [model, setModel] = useState("Mustang");
  const [year, setYear] = useState("1964");
  const [color, setColor] = useState("red");

  return (
    <>
      <h1>My {brand}</h1>
      <p>
        It is a {color} {model} from {year}.
      </p>
    </>
  )
}

As you start using hooks, you'll soon realize how much of an improvement they are over the old school way of writing React. Very nice (Borat voice)!

useEffect Hook

Now that we've taken a look at the useState hook, it's time to look our next commonly used hook, useEffect.

The useEffect hook allows us to perform "effects" onto our components.

The useEffect hook is commonly used for tasks like fetching data or directly updating the DOM.

useEffect accepts two arguments. The second argument is only optional.

useEffect(, )

Let's create an Example by creating a timer.

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Timer() {
     const [count, setCount] = useState(0)

     useEffect(() => {
          setTimeout(() => {
              setCount((count) => count + 1)
          }, 1000)
     })

     return <h1>I've rendered {count} times!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

It runs, but it keeps counting. I thought it was only supposed to count once?

useEffect runs on every render. So, when the count changes, a render happens, which then triggers another effect.

Obviously this is not what we want. There is a multitude of ways to control when our side effects are triggered.

We have to remember to include the second parameter that accepts an array. We can also pass dependencies into useEffect in this array.

No dependency passed:

useEffect(() => {
     //Runs on every render
})

An empty array:

useEffect(() => {
     //Runs only on the first render
}, [])

Props or state values:

useEffect(() => {
     //Runs on the first render
     //And any time any dependency value changes
}, [prop, state])

So, if we want to fix our issue, we need to an empty array as a dependency so we only run this effect on the initial render.

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Timer() {
     const [count, setCount] = useState(0)

     useEffect(() => {
          setTimeout(() => {
              setCount((count) => count + 1)
          }, 1000)
     }, []) // <- add empty brackets here 

     return <h1>I've rendered {count} times!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

Dependent on variable:

This will make the useEffect hook dependent on a variable. If the count variable updates, the effect will run again.

import { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Counter() {
  const [count, setCount] = useState(0);
  const [calculation, setCalculation] = useState(0);

  useEffect(() => {
    setCalculation(() => count * 2);
  }, [count]); // <- add the count variable here

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <p>Calculation: {calculation}</p>
    </>
  );
}

ReactDOM.render(<Counter />, document.getElementById('root'));

If there are multiple dependencies, they should be included in the useEffect dependency array.

Effect Cleanup

Some effects require cleanup to reduce memory leaks.

Timeouts, subscriptions, event listeners, and other effects that are no longer needed should be disposed.

We do this by including a return function at the end of the useEffect Hook.

Example:

Clean up the timer at the end of the useEffect Hook:

import { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    let timer = setTimeout(() => {
    setCount((count) => count + 1);
  }, 1000);

  return () => clearTimeout(timer)
  }, []);

  return <h1>I've rendered {count} times!</h1>;
}

ReactDOM.render(<Timer />, document.getElementById("root"));

Note: To clear the timer we had to name it.

Now we have learned and went through some different use cases for the useState and the useEffect Hooks.

Next week we will continue our discussion on Hooks and expand into the useRef and useContext Hooks.

 
Share this