Skip to main content

React Fundamentals - Using and Updating State

Learning Objectives

Students Will Be Able To:
Add State to a Function Component
Pass State to Children Components
Update State
Explain Why Not All Data Needs to be State

Road Map

  1. Setup
  2. Previous Lesson's Bonus Challenge
  3. Review of State
  4. Intro to React Hooks
  5. Add State to a Function Component Using the useState Hook
  6. Using the Value of the State
  7. Updating State Using the useState Hook's Setter Function
  8. More About State
  9. Not All Data Needs to Be State
  10. React Fundamentals Chart Update
  11. Essential Questions
  12. Further Study

Videos

Video 📹 Link

1. Setup

This lesson continues to build upon the "React To-Do" sandbox we've been working with during the last couple of lessons in codesandbox.io:

2. Previous Lesson's Bonus Challenge

If you didn't have a chance to complete the challenge exercise of numbering the to-dos, make the following changes:

  1. Add a <div> to <ToDoListItem> before the {todo}:

    ToDoListItem.jsx
    <div className="flex-ctr-ctr">{index + 1}</div>;
    {
    todo;
    }
  2. Add that general purpose flex-ctr-ctr class to styles.css:

    styles.css
    .flex-ctr-ctr {
    display: flex;
    justify-content: center;
    align-items: center;
    }
  3. Update ToDoListItem.css to the following:

    ToDoListItem.css
    .ToDoListItem {
    display: flex;
    justify-content: flex-start; <!-- default -->
    align-items: center;
    margin: 4vmin;
    padding: 2vmin;
    border: 1vmin solid purple;
    list-style: none;
    font-size: 6vmin;
    }

    .ToDoListItem div {
    width: 8vmin;
    height: 8vmin;
    margin-right: 4vmin;
    background-color: black;
    color: white;
    border-radius: 50%;
    }

3. Review of State

Just in case you haven't gotten it tattooed yet...

info

State is the data a program needs to "remember" while it's running

State, the facts 😁:

  • State is the single-source of truth of an application.
  • State changes due to user interaction or when other events occur such as when a timer ticks.
  • When state is updated, the user interface needs to be re-rendered to reflect the new state of the program - this fundamental concept might sound familiar from Unit 1.
  • React state is immutable, this means that it cannot be changed, you cannot change the value of a state object or change the value of its properties unless you use the setState function created with the given state object when you use the 'useState' function. When you update state using a set state method, you are in fact creating a new version of that state to be used instead of the older version, so think of set state more as creating an updated version of your state object and not that it is updating the existing state object.

❓ What state do we have in "React To Do"?


The todos array


tip

👀 In a React app, UI-related state is used to make a user interface render dynamically. For example, if we wanted to hide/show a <PlayerDetails> component, we would use a piece of state like showPlayerDetails to track whether or not to render the component.

4. Intro to React Hooks

Hooks were added to React in version 16.8.

info

Hooks allow components defined as functions to perform functionality that used to require that the components be defined as a class.

Consider what happens when React renders a Function Component:

  1. React calls the function passing to it props as an argument.
  2. The function runs and ultimately returns its UI (the JSX).
  3. React uses the object returned by the JSX to render the UI.

Now consider that when a function finishes running and returns, its scope is destroyed, thus any data held in variables is forgotten!

So, how can a Function Component remember state?

Some of you might be thinking to yourselves - What about using closures?

Exactly! Indeed, it's closures that enable hooks to remember state, as well as implement other stateful behavior!

Some facts about React hooks:

  • Hooks are a way to add reusable stateful behavior to Function Components, not class-based components.
  • Hooks themselves are functions and must be named beginning with the word use, e.g., useState.
  • Hooks must be called at the top-level of the component's function and cannot be called conditionally. For example, a hook cannot be called within an if statement. Basically, React must be able to depend on every hook being called in the exact same order every time the component renders (is invoked by React).
  • It's possible to create custom hooks if the built-in hooks don't satisfy our needs, in fact, lots of custom hooks are just a google away!

5. Add State to a Function Component Using the useState Hook

React's useState Hook is used to remember and update state in a Function Component.

Let's briefly review the doc linked to above. As you can see, the Function Component is more concise and likely easier to reason about.

Also, Function Components eliminate having to worry about the proper binding of this, which definitely becomes an issue when a Class Component needs to invoke an event handler.

Adding the todos Array as State

Although the current todos array in App.js can be updated using push, etc., a very important thing won't happen if we do.

❓ What won't happen?


A re-render will not be triggered!


Before state can be added to a component, we need to import the useState named export from the React library:

App.js
// App.js
import { useState } from "react";

The above syntax is used to import named exports. JS modules can have any number of named exports and only a single default export.

Next, inside of the Function Component we invoke useState and provide the initial value of the state as an argument:

Live Editor
// remember to export default
function App() {
  const [todos, setTodos] = useState([
    "Have Fun",
    "Learn React",
    "Learn the MERN-Stack",
  ]);

  // ignore the code below -> needed for live editor
  return JSON.stringify(todos);
}
Result
Loading...
info

👀 The initial value argument is only used to set the state's value when the component is rendered for the first time

The useState hook/function always returns an array with two elements where:

  • The 1st element is the current value of the state
  • The 2nd element is the setter function used to update the state's value

❓ So what's that
[todos, setTodos] = ...?


Yes, that's array destructuring assignment!

The call to useState() always returns an array with two elements and now we have two variables we can use to easily access those elements.


6. Using the Value of the State

Did you notice that the To-Dos have continued to render just fine?

Using the value of the state is as simple as using the first element returned by useState(), which in our case, has been destructured into a variable named todos - and that variable (todos) continues to be passed to the <ToDoList> components as a prop.

We are free to use the variable holding the state's value any way we want, pass it as a prop, render it, etc.

danger

However, we do not modify the state's value by reassigning to the state variable. Instead, updating state is the job of the setter function...

7. Updating State Using the useState Hook's Setter Function

When we invoked useState(), we destructured the returned array naming the second element setTodos because it is a setter function used to update the todos state to whatever we pass to that setter function.

By convention, we always name the setter function by pre-pending the word set to the name we assigned to the state value variable and adjusting the camelCasing.

For example, if you wanted to track the state of the board in a Tic-Tac-Toe app:

export default function App() {
const [board, setBoard] = useState([0, 0, 0, 0, 0, 0, 0, 0, 0]);
...
}

❓ What line of code could you use to define state to track whose turn it is in a Tic-Tac-Toe app?


// 1 represents player 1; -1 is player 2
const [turn, setTurn] = useState(1);

We'll wait until the next lesson to update todos. For now, we'll practice with a different piece of state...

👉 You Do - Add a New Piece of State (1 min)

  • Add a showTodos state to "React To-Do" and initialize it to true;

Rendering Conditionally

Now we can use the showTodos state to conditionally render the <ToDoList> component:

export default function App() {
...

return (
<div className="App">
<h1>React To-Do</h1>
{/* Conditionally render ToDoList */}
{showTodos && <ToDoList todos={todos} />}
</div>
);
}

We told you in Unit 1 how handy those logical operators like && would be! && is used extensively in React to conditionally render a component or not.

❓ What kind of JS expression would we use to render one of two components?


A ternary expression! For example:

export default function App() {
const [user, setUser] = useState(getUser());

return <div className="App">{user ? <HomePage /> : <LoginPage />}</div>;
}

Cool, the To-Dos are still being rendered because showTodos is true.

Let's use React Developer Tools to update the state:

Toggling between true/false will result in <ToDoList> being rendered or not - very cool!

info

👀 React Developer Tools have no way of knowing what we named our state - all it knows is the order in which the useState hooks were called.

Replace, Don't Mutate State

caution

Invoking the setter function will replace the value of the state.

Replacing primitive data types, e.g., numbers, strings & booleans, is gravy. However, we should always replace objects/arrays (reference types), as well.

For example, in the next lesson we will be adding new to-dos to the todos state array. When doing so, we will not use the push method, which mutates the array instead of replacing it with a new array.

The modern approach is to use spread syntax for creating new arrays and objects from existing ones.

Here's a preview of how we might add a new to-do:

function handleAddTodo(todo) {
// Create a new array that includes the new todo
const newTodos = [...todos, todo];
// Update state and re-render
setTodos(newTodos);
}

The ... is the spread syntax and is used similarly to "spread" out the existing properties in objects.

tip

The important thing to remember here though is to replace objects/arrays, not mutate them!

Updating State is an Asynchronous Operation

Calling the useState hook's setter function is an asynchronous operation.

This means that any synchronous code following the call to the setter will be accessing the "current" value of the state variable, not the updated value.

The state's value won't actually be updated until the subsequent render.

This sandbox demonstrates this asynchronous behavior.

Triggering a Re-Render

A component will re-render when a state's value is updated by calling its setter function!

❓ When a component renders its child components in its JSX, what do those child components do?


They render their children and so on...


8. More About State

State/Information Can Only Be Passed Down

❓ State can only be passed DOWN the component hierarchy via _______.


Props


Typically, you'll find that most state needs to be defined in the <App> component.

However, we can avoid unnecessary "prop drilling" by moving state to the nearest parent component in common with the other components that need to use that state.

❓ If a piece of state needed to be used only by the green and black components, where should the state be defined?


In the green component


❓ If a piece of state needed to be used by the dark blue and light blue components, where would the state be defined?


In the top (yellow) component, which typically would be <App>.


Categorizing State

We can think of there being three categories of state:

  • Data-related state
  • UI-related state
  • Computed state

Data-related state holds the data that the application is about. For example, in "React To-Do", it's the todos array.

It's common to define all state that represents data first in <App>. This makes it possible to pass that state to any component that needs it.

Then later, the app can be refactored to move state down the hierarchy to avoid having to pass the same prop through multiple components that don't need it and are simply passing the prop down the hierarchy to its ultimate destination. This is known in React as "Prop Drilling".

"Prop Drilling" is the way React is designed to work, however, it can be a bit tedious and has led to alternative approaches to delivering state directly to the component that needs it vs. being passed down the hierarchy via props. Some alternative approaches are listed in the Further Study section.

UI-related state is state used to control the dynamic rendering of the UI. For example, you click an [EDIT] button and an <input> is displayed instead of a <div>.

❓ We've actually defined UI-related state already. What's the name of the variable holding the UI-related state?


showTodos


UI-related state is usually defined in the component that uses it and since no other component would likely need to access its value.

However, if for some reason the UI-related state needed to be accessed higher up in the hierarchy, then it would need to be defined high enough to be accessed by all components that need to access it.

Computed State

Some state has its value computed from other state.

A good example of computed state would be winner state in the game of Tic-Tac-Toe.

❓ What state would be used to compute winner?


board


Since computed state is derived from other state, computed state does NOT have to be defined using useState (see example below).

How Many State Variables Can We Use?

A Function Component can call useState as many times as necessary. For example:

export default function App() {
const [board, setBoard] = useState([0, 0, 0, 0, 0, 0, 0, 0, 0]);
const [turn, setTurn] = useState(1);
// winner is computed using board state
const winner = getWinner(board);
...
}

However, it's not always necessary to break up state into multiple useState() calls because a single state can be a JS object which would hold any number of values in its properties.

For example, if we needed to hold the state for a sign up form, we can do this:

export default function SignUpPage() {
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
pwConfirm: "",
});
}

Instead of this:

export default function SignUpPage() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [pwConfirm, setPwConfirm] = useState("");
}

Grouping related state can result in less code required to update that state (fewer setter functions) and also makes it easier to send that state to the backend.

9. Not All Data Needs to Be State

Not all data needs to be defined as state.

Consider the Tic-Tac-Toe app where we might use a WINNING_COMBOS array that held nested arrays with winning index patterns.

❓ Why wouldn't WINNING_COMBOS need to be defined as state?


WINNING_COMBOS is a constant lookup data structure that never changes.


tip

👀 Only data that you want to cause a re-render when it changes should be defined as state.

How to Persist Non-State Data

Since variables are forgotten after a function returns, how we do we persist non-state data?

It depends upon whether a component needs its own copy of that data or not...

Component Does NOT Need Its Own Copy of the Non-State Data

When there only needs to be one copy of some data because it can be shared by all components, we can simply define a variable outside of the Function Component.

For example:

const WINNING_COMBOS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];

export default function App() {
const [board, setBoard] = useState([0, 0, 0, 0, 0, 0, 0, 0, 0]);
const [turn, setTurn] = useState(1);
// winner is computed using board state
const winner = getWinner(board);
...
}

Each Component Needs Its Own Copy of the Non-State Data

If a Function Component needs to remember its own copy of non-state data between renders, it can't define a variable in or outside of the function.

An example of this situation might be a list of components that each have their own timer created with setInterval. It would be necessary for each component to remember the id of its timer so that it can clearInterval at some point.

There is another hook for this scenario: useRef.

There's no need to cover it now, but it's good to know that the useRef hook allows a Function Component to remember non-state data between renders. Also, data remembered using a "ref" is mutable and can be updated without triggering a re-render.

10. React Fundamentals Chart Update

Yup, time to update our React Fundamentals Chart before wrapping up with the Essential Questions.

React FundamentalSummary
......
State
  • When a component's state is updated, the component re-renders.
  • Most state is data-related, i.e., related to the purpose of the application. However, a component can use UI-related state to control the dynamic rendering of its UI.
  • The useState hook is used to manage state in a Function Component.
  • Invoking useState(<initial value of the state>) returns an array whose first element is the state's current value and whose second element is a setter function used to update the state's value.
  • Only data that you want to cause a re-render when it changes should be defined as state.

11. ❓ Essential Questions (2 mins)

(1) In general, why were hooks added to React?


Hooks allow Function Components to "remember" state and implement stateful behavior that used to require Class Components.


(2) What React hook is used to manage state in a Function Component?


useState


(3) How do we update the value of state?


By calling the state's setter function.


(4) True or False: Only data that you want to cause a re-render when it changes should be defined as state.


True


12. Further Study

Learn More About Hooks

To learn more about hooks, read Introducing Hooks in the docs.

Built-in Hooks and their use cases

HookUse Case
useState()Used to manage state in a Function Component.
useEffect()Used to implement "side effects", e.g., fetching data, using timers, subscriptions, etc.
useEffect() implements the functionality of class components' componentDidMount, componentDidUpdate & componentWillUnmount with a single hook!
useRef()In addition to how refs are used to access DOM elements in class components, useRef() can be used more generally to "remember" any non-state data that needs to be persisted between renders similar to how we use instance properties in class components.
useReducer()An alternative to useState() for when the state is more complex. It uses a reducer function and "actions" to update state - similar to how Redux does (but not as comprehensive).
Other built-in hooksuseContext()
useMemo()
and other less common hooks here...

Alternative State Management Approaches

Note that although there are alternatives to using the useState hook, be aware that there's a learning curve with each alternative and that no alternative should be considered until you have mastered the fundamentals of React.

Some alternatives to using useState are:

  • Redux is a popular real-world state management alternative. However, it requires quite a bit of setup and is overkill for most apps. Although still popular, Redux has been falling out of favor lately.

  • React's Context API can be combined with the useReducer hook to provide a lighter-weight alternative to Redux.

  • MobX is another popular alternative, like Redux, it's popularity has probably peaked.

References