Home

React Hooks

React 16.8+ supports Hooks.
With Hooks, we can use simple function components to do lots of the fancy things that we could only do with class components in the past.

React Hooks, plainly put, are functions that let us manage the internal state of components and handle post-rendering side effects directly from our function components.

Hooks don’t work inside classes — they let us use fancy React features without classes.
Keep in mind that function components and React Hooks do not replace class components.
They are completely optional; just a new tool that we can take advantage of.

React offers a number of built-in Hooks.
A few of these include...
useState(), useEffect(), useContext(), useReducer(), and useRef().



There is a full list Here that you can look at.

useState() hook

The most common hook is probably the useState() hook.
It is a named export from the React library
and is imported like this...
import React, { useState } from 'react'

useState() is a JavaScript function defined in the React library.
When we call this function, it returns an array with two values:-

current state
The current value of this state

state setter
A function that we can use to update the value of this state

Because React returns these two values in an array, we can assign them to local variables, naming them whatever we like.
For example:
const [toggle, setToggle] = useState();

Top

Example Code with useState()

import React, { useState } from "react";
 
function Toggle() {
  const [toggle, setToggle] = useState();
 
  return (
    <div>
      <p>The toggle is {toggle}</p>
      <button onClick={() => setToggle("On")}>On</button>
      <button onClick={() => setToggle("Off")}>Off</button>
    </div>
  );
}
The above code will create a toggle variable (with no value).
And (when you click on a button) will use setToggle() to set the value of toggle to Off or On, depending on which button was clicked.

Using this 'hook' there was no need to worry about binding functions to class instances, working with constructors, or dealing with the this keyword.
With the State Hook, updating state is as simple as calling a state setter function.
NOTE ALSO that toggle was set as a const variable but setState() was still able to reset the value.


Calling the state setter also signals to React that the component needs to re-render.
The magic of useState() is that it allows React to keep track of the current value of state from one render to the next!

Example 2

import React, {useState } from 'react';

export default function ColorPicker() {
  const [color, setColor] = useState();

  const divStyle = {
    backgroundColor: color ? color : 'purple'
  };

  return (
    <div style={divStyle}>
      <p>
        The color is {color ? color : 'Purple (default)'}
      </p>
      <button onClick={() => setColor("Aquamarine")}>
        Aquamarine
      </button>
      <button onClick={() => setColor("BlueViolet")}>
        BlueViolet
      </button>
      <button onClick={() => setColor("Chartreuse")}>
        Chartreuse
      </button>
      button onClick={() => setColor("CornflowerBlue")}>
        CornflowerBlue
      </button>
    </div>
  );
}

In the above I am using useState() to create color and setColor.

I am creating divStyle which sets the backgroundColor of the div.
It uses the Ternary Operator (a ? b : c) so if color is true (not blank) it will show set it = to the value of color otherwise it will set it to be 'purple'.

Then when you click on a button it uses setColor() to change the value of color and that will force a render and change the backgroundColor

Top

Setting a default value in useState()

Sometimes you will need to set a default value for the property created by useState()

For example, we may want the default colour in the above examples to be Green. This can be done by...

const [color, setColor] = useState("Green")



Though most common would be to set the value to either TRUE of FALSE, eg...
const [isLoading, setIsLoading] = useState(true)

So when you FIRST check the value of isLoading it will = true

OR

maybe the setting of a value to 0 , eg...
const [counter, setCounter] = useState(0)

So when you FIRST check the value of counter it will = 0

Using a 'callback function'

Often, the next value of our state is calculated using the current state.
for example...adding 1 to the current value

In this case, it is best practice to update state with a callback function.
If we do not, we risk capturing outdated, or “stale”, state values.

In this example (below) I am creating a property (called: increment) that is a function to add 1 to itself using the 'callback function' within the state hook (setCount). Eg...

const [count, setCount] = useState(0); 
const increment = () => setCount(prevCount => prevCount + 1); 
The best I can see is...
you put the prev in front of whatever value property you used (in this case count) to make it prevCount (but make it camelCase).
You can probably call it anything you like as the system knows the property is count as it looks back to the useState() command to find it...
...but protocol says to keep it simple, stupid (KISS) and make this example prevCount.


NOTE
We can just call setCount(count +1) and it would work the same in this example...
...but it is safer to use the callback method.
(for reasons that my course did not go into as it was too big a topic to discuss)
you can see this document for further information as to why but it may have something to do with...


"Think of setState() as a request rather than an immediate command to update the component.
For better perceived performance, React may delay it, and then update several components in a single pass.
React does not guarantee that the state changes are applied immediately."


Top

Another little example

import React, { useState } from 'react';

export default function QuizNavBar({ questions }) {
  const [questionIndex, setQuestionIndex] = useState(0);

  const goBack = () => {
    setQuestionIndex(
        prevQuestionIndex => prevQuestionIndex-1
    );
  }

  const goToNext = () => {
    setQuestionIndex(
        prevQuestionIndex => prevQuestionIndex+1
    );
  }
  // determine if on the first question or not 
  const onFirstQuestion = 
    questionIndex === 0;

  const onLastQuestion = 
    questionIndex === questions.length - 1;

  return (
    <nav>
        <span>Question #{questionIndex + 1}</span>
        <div>
            <button 
                onClick={goBack} 
                disabled={onFirstQuestion}>
                    Go Back
            </button>
            <button 
                onClick={goToNext} 
                disabled={onLastQuestion}>
                    Next Question
            </button>
        </div>
    </nav>
  );
}

The explanation of left pane

questionIndex is given a starting value = 0, by the...
useState(0) command.

goBack is a function that decreases the questionIndex value by 1...
...the setQuestionIndex() does this through the use of the callback function...
...prevQuestionIndex => prevQuestionIndex-1

goToNext() is similar to goBack() but ADDS 1 to the value instead of reducing the value of questionIndex

onFirstQuestion returns TRUE (if questionIndex is = 0) or FALSE (if it is not equal to 0)
This says that onFirstQuestion = questionIndex === 0
and here the command questionIndex === 0 is not a calculation it is just more of a question, so if the value of questionIndex is 0 then it returns TRUE (if not it returns FALSE).


onLastQuestion checks if the value of questionIndex is equal to the number questions there are and if so return TRUE (otherwise return FALSE).

And in the main return section the button to goBack fires the onClick event - which is the goBack() function - and then reduces the count.
The disabled event calls onFirstQuestion() and if it gets back TRUE it will disable the button.

While the goToNext button adds 1 to the count and disables the button if it is at the maximum number of questions.

Top

One more example

import React, { useState } from "react";
import ItemList from "./ItemList";
import { produce, pantryItems } from "./storeItems";

export default function GroceryCart() {
  const [cart, setCart] = useState([]);

                const addItem = (item) => {
    setCart((prev) => {
      return [item, ...prev];
    });
   };

                const removeItem = (targetIndex) => {
    setCart(
      (prev) => {
        return (
          prev.filter((item, index) => index !== targetIndex);
        )
      }
    );
  };

  return (
    <div>
      <h1>Grocery Cart</h1>
      <ul>
        {cart.map((item, index) => (
          <li onClick={() => removeItem(index)} key={index}>
            {item}
          >/li>
        ))}
      </ul>
      <h2>Produce</h2>
      <ItemList items={produce} onItemClick={addItem} />
      <h2>Pantry Items</h2>
      <ItemList items={pantryItems} onItemClick={addItem} />
    </div>
  );
}



The 3 dots (...) syntax (see ...prev in the addItem function, above) is not unique to React and is used by two operators:
Spread Operator (Spread Syntax)
Rest Operator (Rest Syntax)

The Spread operator lets you expand an iterable like an object, string, or array into its elements while
the Rest operator does the inverse by reducing a set of elements into one array.

See this document for more info on Spread Syntax.

One more explanation

The pane on the left is part of the code for choosing food items from two different lists (Produce and Pantry items).

Here, though, I just want to expand on two things...
addItem()
removeItem()



addItem()
will add items to your array (cart) as used in the useState() hook.

When the food item you want is clicked on (onItemClick={addItem}) it will run the function const addItem...

The item passed to the function const addItem will be the food item clicked on (it comes from the imported class(es)).

The return [item, ...prev] line of code is used to return a new array...
With the new item as the first item in the new array.
And the ...prev refers to the array (... means it's an object (array) I think) so it will add the items in the prev array after the new item.



removeItem()
The prev.filter((item, index) => index !== targetIndex) command will...

filter out (remove) the item you clicked on to be removed from the list.

prev.filter is the array we are to be working with (it knows which array by the use of setCart()).

The (item, index) it grabs as it reads through the array (automatically happens in the.filter function).

And the index !== targetIndex means if the index value of the item in the array it is currently dealing with is NOT EQUAL to the value of targetIndex (passed to it via the onClick event - see removeItem(index)) then it will NOT REMOVE the item from the array.

Top

Grouping variables in an Object

When working with variables it can be useful to group them in an object.

const [profile, setProfile] = useState( {} )
creates a 'state hook' variable called profile
the {} tells React to create it as an EMPTY OBJECT.

Then when we want to populate the profile object we would normally say something like...

setProfile(
    myName: "Gary",
    myTest: "Test",
)


which would create profile.myName (the value would be "Gary")

Grouping variables in an Object (2)

When working with variables it can be useful to group them in an object.

const [profile, setProfile] = useState( {
   title: "Best Musical Ever",
   actors: ["George Wilson", "Tim Hughes", "Larry Clements"],
   locations: {
   Chicago: {
   dates: ["1/1", "2/2"]
} )

creates a 'state hook' variable called profile
this time it populates profile when created

and refer them as profile.title (the value would be "Best Musical Ever")

This example also creates profile.actors as an array.

And profile.locations as an object which holds profile.locations.Chicago.dates and .dates itself is an array.

Top