-
Notifications
You must be signed in to change notification settings - Fork 0
Home
This training module is designed to get you started with React by briefly covering some of the fundamental concepts and then demonstrate how to build a basic TicTacToe application with React. It will also introduce Vite, a front-end toolchain that makes developing with React quick and easy.
- Node.js (v16 or higher)
- You can check your installation and version by typing
node -v
in a terminal. - You can download Node.js here
- You can check your installation and version by typing
- Visual Studio Code (Recommended)
- Good understanding of both HTML and JavaScript
- Basic understanding of JS object destructuring assignment, arrow functions, and ES6 Modules (import/export)
This training module is intended to help people new to React understand some of its basic concepts. This module will start by explaining why someone would be interested in using React, along with explaining one of the popular ways of starting a React project. After providing this introductory background, we will explain how you can use jsx to create a web page, explain what React components are and their lifecycle, and provide an introductory explanation of what React hooks are and how to use them.
- Create a starter project with Vite
- Use JSX format to dynamically render HTML
- Create and use a function component
- Understand the lifecycle of a component
- Understand state management using React Hooks
React is a declarative JavaScript library for creating user interfaces. It is used in many popular large scale websites such as Facebook, Instagram, AirBnB, Netflix, and many more. React is primarily made up of three main parts that we will cover in this module: Components that can be separated and organized by their purpose and reused, hooks that allow you to persist state between renders and perform actions at specific parts of the components lifecycle (more below), and JSX that allows you to use an HTML-like syntax alongside JavaScript to create a webpage.
Some benefits of using React include:
- It's flexible. React is an unopinionated framework, which means developers have the freedom to structure their applications in whatever way they prefer. This allows both small-scale and large-scale applications to be easy to read, refactor, and extend.
- It's powerful. React handles a lot of tedious actions under the hood, such as updating the DOM in response to changes, meaning you can write less boilerplate code and more business code.
- It's fast. React has optimized the operation of updating the DOM (the HTML content of a page) by only changing the content that is necessary. It does this all for you, which improves the developer experience. It also has a small file-size footprint compared to its competitors, making the initial page load faster even on slower connections.
- It has great support. React was built and is maintained by a team at Facebook and the open-source community. It is used in thousands of major websites worldwide, has millions of downloads per week, and is one of the top repositories over on GitHub. It's popularity and vast use means it has great support for any issues that arise, tons of add-ons developed to solve common problems, and much more.
React applications are often called Single Page Applications (SPAs) because instead of splitting your website into many HTML pages, you declare a set of React components and let react handle which elements are being shown on the page based on the application's state and user activity (such as clicking a button).
Before learning React, you should be familiar with the fundamentals of both HTML and JavaScript, including newer features such as arrow functions and destructuring assignment, as these are concepts that are widely used in the world of React and will be utilized but not taught in this module.
Vite is a frontend development toolchain that makes working with React painless to set up. It provides you with the initial code structure, a development server with Hot Module Reloading (HMR) that allows you to quickly see your changes live, and build tools to prepare your application for deploying in a production environment.
To start a React project with Vite, you will need Node v16+ installed on your machine, as well as terminal access. If you are developing in VSCode, you can open a terminal in the current folder by pressing *CTRL + SHIFT + * on your keyboard or via the Terminal menu at the top. You can also check what version of node you have installed by typing
node -v` in the terminal. If these are set up properly, you can now create a React application using the following steps:
- Ensure that VSCode & your terminal are opened in the folder you want to start your project. The next step will account for whether or not your folder already has files inside
- Type
npm create vite folderName
in the terminal. The folder name can be replaced with a.
to use the current folder, but this will delete everything currently in the folder if you typey
for the next prompt - From the next two lists, use your enter and arrow keys to select React, then JavaScript
- If you specified a folder name in the previous step then you will need to enter the newly created folder using
cd folderName
- Type
npm install
in the terminal to have Node install all of the React dependencies. This step should also be done for any React applications that you download since you need to locally install these dependencies - Type
npm run dev
in the terminal to start the development server. You can view the page live by either copy/pasting the link provided in the terminal into a browser, or CTRL + Clicking the link.
Congratulations on starting a React application! Now you can start customizing your application to suit your needs. If you only want to use the App.css file for visual customizations, remember to delete references to the other one from index.html.
If you created a React application in the previous step you may have noticed that the index.html
file does not contain any of the page's content. In fact, it is mainly just the boilerplate HTML structure, such as the <head>
section with title and meta elements, and an entry point to the JavaScript of your application. So how are we seeing a counter button, images, and other content on our live page? This is all because of React + JSX.
JSX is an HTML-like syntax that is converted to proper JavaScript code by the dev and build tooling (in this case, Vite). JSX allows you to combine HTML and JavaScript to create webpage elements in an intuitive way, similar to templating languages in other web frameworks. For example, the default page created by Vite (in App.jsx
) has a button with a number that increments with each click. While plain JavaScript usually requires several parts to make this button work, React can accomplish it with just two: First is a useState
hook which will be discussed later on, and second is the button that utilizes the hook and displays its information like so:
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
You can embed JSX almost anywhere within a JavaScript file with the extension .jsx
. This file extension tells Vite that there is React JSX code that will need to be transpiled during the build step. Most commonly, you will see JSX following a return
statement of a function, like so:
function App() {
return (
<div>
<h1>Hello world!</h1>
</div>
)
}
This is also known as a component. We'll discuss this more in the next section.
You can also use JSX elsewhere, such as the value of a variable:
let myHeading = <h1>Hello world!</h1>
To embed a JavaScript object (such as an array, string, number, and more) in JSX, simply wrap the expression inside curly braces ({}
), similar to other templating languages.
// assume favColor = "purple" and was declared beforehand
<p>My favorite color is {favColor}</p>
<p>Two plus two is {2+2}.</p>
Components in React are a way of splitting your webpage into individual, reusable pieces. Essentially, a component is a JavaScript function that returns JSX and can optionally accept properties as arguments.
Here is an example of a simple function component:
function ColorDisplay({ color }) {
return <p>My favorite color is {color}.</p>
}
Note that this function is using object destructuring syntax to read the component's properties. This is a React convention and is equivalent to the following:
function ColorDisplay(props) {
return <p>My favorite color is {props.color}.</p>
We can now use this component as a child of other components with the following JSX syntax:
<ColorDisplay color="purple" />
Another recommended React convention is to place each component in its own unique file. If you do this, be sure to export the function and import it in any other file you wish to use it in. If you aren't sure how to do this, refer to the article on ES6 Modules at the top of this training module or explore the code used in the Lab below for an example.
If you take a look at the default app structure provided by Vite using npm create
, you might notice that App
is, itself, a component. This is crucial to understand because components are the fundamental building blocks of a React interface, not a lesser used extension of its capabilities.
Let's look at how we might add our ColorDisplay
element to our App.
// ColorDisplay.jsx
// using arrow function syntax here
export const ColorDisplay = ({ color }) => {
return <p>My favorite color is {color}.</p>
}
// App.jsx
import { ColorDisplay } from "./ColorDisplay"
function App() {
return (
<div>
<h1>My favorites</h1>
<ColorDisplay color="purple" />
</div>
)
}
Hooks are a feature of React that allow you to hook into and take advantage of React's "under the hood" features, like managing state or running code on render, using special functions. If you created an application in an earlier section, then you may have noticed that instead of the code for the button needing to reference this
a lot (i.e. <button onClick={() => this.setState({ count: this.state.count + 1 })}>
), it uses a function called setCount
like this: <button onClick={() => setCount(count + 1) }>
. You may also notice that the process of creating count is far shorter than usual. This is due to the useState
react hook, which will be explained in more detail, along with two other common React hooks, useEffect
and useRef
. If you are having trouble using any of these hooks, make sure you have imported them at the top of the file. If all three are being used the import line would look similar to this:
import { useState, useEffect, useRef } from 'react'
useState
is a function that allows data to persist even after the component rerenders. It does this by providing a state variable containing the data and a function that updates the data, and manages the persistence for you. Without it, a regular variable (such as let count = 0
) will be reset to its initial value (0
in this case) on each render. useState
requires just one argument which will be used as the default value of the data and can be anything you'd like, though it is recommended to use the same data type that it will contain later on.
Let's look at the useState
hook that is in our default Vite app:
const [count, setCount] = useState(0)
In this case, the initial value for count
is 0
.
While we have spent some time evaluating the right half of this declaration, the left half is equally important to understand, especially the setCount
portion, which you should recognize from the introduction. useState
actually returns an array with two values, and we use destructuring assignment to declare two variables. Note that the following syntax, while less elegant, would achieve the same result:
const countState = useState(0)
const count = countState[0]
const setCount = countState[1]
As you can see, the destructuring assignment allows us to condense these three lines in to one that is easier to read.
Back to our example...
const [count, setCount] = useState(0)
In this example, count
is the variable that receives the default value, while setCount
is a function that can be called later to change this value.
The count
variable is updated by React upon each rerender to contain the data, and if we want to give React a new value for this data, we use the setCount
function.
To set the state to a value that is independent of its previous value, you simply pass the new value as an argument like so:
setCount(5)
To set the state to a new value based on its current value, you pass in a function receiving on argument, and return the new value in that function. This sounds confusing, so let's see a couple examples.
setCount(function(oldCount) {
return oldCount + 1
})
We can also write this using arrow function syntax, and when reduced to one line, the return
is implied. Example:
setCount((oldCount) => oldCount + 1)
useEffect
hooks allow you to execute a function after a component finishes rendering, among other uses. This is useful for situations where you want the application to figure something out after everything else has been updated. Use effect can take two arguments, a function that is run, and an optional dependency array at the end. For example, if you wanted to update the basic application that Vite creates to change the title every 10 clicks, you could add something like this above the App's return.
useEffect(() => {
console.log("Count changed to " + count)
if (count % 10 == 0 && count != 0){
document.title = “Over ${count} clicks”;
}
});
You can have multiple useEffects in the same file, which is useful for situations where you want certain things to occur only when a specific variable is changed. It also makes code more readable by dividing unrelated actions into their own useEffect
s. For example, if the default Vite application had two buttons, one for count1
and one for count2
, the previous code could be updated to make the document title only use count1’s total, and as a result you only need this useEffect to run when button1 is clicked. To do that a useEffect similar to the one below could be used.
useEffect(() => {
console.log("Count1 changed to " + count1)
if (count1 % 10 == 0 && count1 != 0){
document.title = “Over ${count1} clicks”;
}
}, [count1]);
The dependency array can accept any number of variables, including zero variables, which is just an empty array as the second argument. Using an empty dependency array allows you to create a useEffect
that only runs the first time the app renders. For example, if you wanted to create a useEffect
for the basic Vite button application that changed the title to something unique after the app initializes, you could use something like the code below.
useEffect(() => {
document.title("Ready for clicks")
console.log("Component loaded")
}, [])
You can also return another function inside of your useEffect
hook, and React will call this function when the component is removed from the page. This can be useful for effects that need cleaning up, such as aborting an in-progress API call, terminating subscriptions and event listeners, and other time-consuming actions.
useEffect(() => {
console.log("Component loaded, requesting data from API")
return () => {
console.log("Component unloaded, aborting API request")
}
}, [])
useRef
is somewhat similar to useState
, with one of the largest differences being the fact that updating the variable created with useRef
will not cause the component to rerender. This is important because if your code includes useEffect
, there may be times that you want to update something on the page without having it trigger. You can also use useRef
to keep track of the reference of an HTML element between renders, and this is the most common application of the useRef
hook. Let's see an example of this.
Declare a reference variable with the default value of null
like this:
const msgDisplay = useRef(null)
Then, use the ref
attribute in a JSX element to tell React to assign the variable to that element:
<p ref={msgDisplay}>No messages yet.</p>
Now we can directly alter that element by accessing the current
property of our msgDisplay
variable, like so:
msgDisplay.current.innerText = "Hello world!"
You can also use useRef
to store ordinary types of persisting data, and updating this data will not cause a component rerender.
const myMsg = useRef("Hello world!")
// in your JSX:
<p>{myMsg.current}</p>
And to update:
myMsg.current = "This will not cause our component to rerender."
To help tie everything together, let's summarize the cycle that a component goes through. It is important to understand how everything happens under the hood to know why and where React hooks are important. We'll discuss this further later in this section.
You can think of a React component like a program on your computer. First, you install the program (initialization & mounting). You are notified later that it has updates available (state has changed), so you install updates (updating). When you are done with the program, you uninstall it from your machine (unmounting) which performs various cleanup actions such as clearing cache or deleting temporary files.
Here is what that process looks like with a React component:
- The component is instantiated along with any declared variables (like
useState
anduseRef
hooks or otherwise). - This is done only once, on page load.
- Component is rendered on the page (the
return
of a functional component) -
useEffect
hooks are called, where dependencies allow. If you only want an effect to run on the first render, you would useuseEffect(myFunc, [])
wheremyFunc
is any function.
- Happens when the component's state is updated (with the setter function provided by
useState
) - The component is rendered again, updating the page's HTML to show changes
-
useEffect
hooks are called, where dependencies allow.
- When a component is removed from the page, it is "unmounted."
-
Cleanup functions returned by your
useEffect
hooks are ran. This is useful for doing things such as aborting pending API calls and more.
By knowing this order of operations, it becomes clear that useEffect
and useState
, in particular, are very powerful hooks. useState
allows us to declare which variables should change how our application looks, and useEffect
allows us to perform certain actions after a page loads or renders, such as calls to an external API, and perform any necessary cleanup operations that may be warranted as a result.
In this section of the training module we are going to develop a simple TicTacToe application that leverages many of the React concepts that were explained above.
Code snippets provided in this tutorial may contain comments // like this...
indicating the location of surrounding code. These comments are purely to help provide context and do not need to be copied into your project.
You can also view a live video demonstration of this lab to follow along.
Start off by downloading our starter files to your local machine, which contain a barebones React/Vite app containing a bit of the boilerplate code and CSS styles for the project.
Open the project in VSCode and open a terminal by clicking Terminal->New Terminal
at the top of your screen.
Run the following command in your terminal to install the necessary dependencies:
npm install
If you get an error here, you most likely do not have Node.js properly installed on your machine, or it is out of date. You can find an installer link down at the bottom of the page, in the Additional Resources section.
Next, we'll start a live development server by running the following command so that we can see our changes locally whenever we save:
npm run dev
The basic concept is to store our X and O placement data in an array and use React to convert that into the 3x3 board on the screen.
The first thing we will need to do is create a variable containing our board's current state.
To do that, we will use the useState
hook to allow this data to persist between renders.
Add the following line at the top of the App function component in App.jsx
:
// function App() {
const [squares, setSquares] = useState(new Array(9).fill(null))
We are going to use a helper function within our App component to create a series of 9 Square
components to represent this array. Take note of the Square.jsx
file, which contains a nearly empty component, and how we have already imported it in App.jsx
using destructuring syntax:
// App.jsx
import { Square } from "./Square";
Add the following function inside the App component:
// const [squares, ...
function renderSquares() {
const arr = [];
for (let i = 0; i < squares.length; i++) {
arr.push(
<Square
key={i}
idx={i}
value={squares[i]}
/>
);
}
return arr;
}
// return (...
Few things to note here. We are using JSX inside of the arr.push()
method, showing that you can utilize JSX elements anywhere within your code. In particular, we have a <Square />
component and are passing a few parameters (properties) with its construction:
-
key
: React requires this parameter whenever you have a bunch of components in an array-like structure. We can't access it directly. -
idx
: Because we can't access the key ourselves, we need to set an extra property to track the element's index in the array. -
value
: This will be what the Square renders inside of it on the screen.
Now, lets add this function inside of our App's return statement JSX, in the board
div:
<div className="board">
{renderSquares()}
</div>
Your app should now look something like this:
Next we need to add our properties to the Square.jsx
and make them display their proper value.
Change the Square component declaration line to look like this:
// Square.jsx
export const Square = ({ idx, value }) => {
Replace the content inside the return statement div with the value
property:
// return (
<div className="square">
{value}
</div>
If you want to make sure that everything is working at this point, you can change {value} to {idx} to ensure it is working. If the squares have numbers in them, then everything is working, and you should switch the idx back to value.
We need another variable to determine which player's turn it is. Just like with our board squares, we are going to use another useState
hook with a default value of true
.
Add the following lines to the top of your App component:
// const [squares, ...
const [turnX, setTurnX] = useState(true)
We're going to make a function within App handle what happens whenever a square gets clicked, and then pass that function to our Square
component.
Let's add the function to our App now:
function squareClicked(idx) {
setSquares((oldSquares) => {
const newSquares = [...oldSquares]
newSquares[idx] = turnX ? "X" : "O"
return newSquares
});
setTurnX((current) => !current)
}
So what's going on here? We are using the setSquares
function to update our squares
state variable. Since we want to base the new value off of the old value, we pass in a function, copy the array using ...oldSquares, modify it based on who's turn it is, and return the new array to be the board's state.
Next, we use setTurnX
with the same function syntax (the return
is implied here because it is one-line) to flip the boolean variable and change turns.
Pass the function as a property to our Square component inside of our renderSquares
function by adding this line to our <Square />
JSX:
// arr.push(
// <Square
// ...
handleClick={squareClicked}
// /> ...
In Square.jsx
, we need to accept the new property and utilize it in a onClick
HTML property in our div opening tag. We'll give the onClick
property an inline function that only calls handleClick
if the square is empty.
Apply these changes by updating the Square.jsx file like this:
// Square.jsx
export const Square = ({ idx, value, handleClick }) => {
return (
<div
className="square"
onClick={() => (value ? null : handleClick(idx))}
>
{value}
</div>
);
};
You should now be able to test the functionality of clicking on a square and watching the alternating 'X's and 'O's appear as you click. But how do we know who's turn it is? Let's add a display for that now.
Change the <h2>
element in the return JSX of App to look like this:
// App.jsx
// <h1 className="title"> ...
<h2>
Player Turn:
<span className="turnIndicator">{turnX ? "X" : "O"}</span>
</h2>
If you want to reset the board at this point, you will need to refresh the page.
We're going to add an element to our App JSX to display the game over message. This time, we'll take advantage of the useRef
hook to store a reference to the message element. We're also going to use another useState
hook to check if the game is over or not.
Let's add both of these hooks now to the top of our App component.
// App.jsx
// const [turnX, ...
const [gameOver, setGameOver] = useState(false)
const winMsg = useRef(null)
Add the "winMsg" span
to our return JSX and use the ref=
attribute to bind this element to our useRef
variable declared in the last snippet.
// App.jsx
// </h2>
<span className="winMsg" ref={winMsg}></span>
// <div className="board"> ...
While we're at it, we can add a guard clause to our squareClicked
function and prevent square clicks after a game ends.
// App.js
// function squareClicked(idx) {
if (gameOver) return
// setSquares( ...
Here is the function that checks for a game winner or draw. It simply loops over the valid win conditions and checks if any of the trios match.
Add this inside the App component above the return statement:
// App.js
function checkWinner() {
const conditions = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (const condition of conditions) {
if (
squares[condition[0]] &&
squares[condition[0]] == squares[condition[1]] &&
squares[condition[1]] == squares[condition[2]]
) {
return squares[condition[0]] // returns "X" or "O"
}
}
// check draw
if (squares.some((val) => val === null)) {
// there are turns left
return false
} else {
return "DRAW"
}
}
// return ( ...
So we have the function that checks for winners, but where do we call it? That is where our first useEffect
hook comes in. The function passed into the useEffect
will run each time the component rendered, after rendering. Since clicking on an open square causes it to re-render, this is the perfect time to check if someone has won the game.
Let's add that hook now, in our App component just after the rest of our hook declarations.
// App.jsx
// const winMsg = ...
useEffect(() => {
const winner = checkWinner();
if (winner) {
setGameOver(true)
if (winner == "DRAW") {
winMsg.current.innerText = "It's a draw!"
} else {
winMsg.current.innerText = winner + " wins!"
}
} else {
winMsg.current.innerText = ""
}
});
First, we check if there is a winner/draw. If not, simply clear winMsg
text. If there is a winner, we need to set the gameOver
state to true
, and because this value is not dependent on previous state we can pass in the value directly instead of using a callback function. After that, the message is once again set to reflect the status of the game. Notice how we are using winMsg.current
to access the current value of the reference winMsg
.
At this point, we can click squares to take turns and the game will detect when the game is over, preventing us from clicking more squares. Now that we have all of our state variables, we can make the reset function and set the state variables back to their defaults.
Add this function to your App component, anywhere after the hooks and before the return statement:
// App.jsx
function resetGame() {
setSquares(new Array(9).fill(null))
setTurnX(true)
setGameOver(false)
}
It's pretty simple, but it's a great demonstration of how powerful React can be. Just by resetting our state variables, our app is back to a clean slate. Let's wire our <button>
up to this function using the onClick=
attribute.
Change the <button>
element to look like this, inside the App component's return statement:
// App.jsx
// <div className="board" ...
<button onClick={resetGame}>Reset</button>
Test your application and ensure that there are no errors. your app now has full functionality!
Time to make things look a little crazy. Let's head over to our Square component and add a hover effect.
We are going to use a useRef
hook to store a reference to the square's <div>
element, and our random
function from the util.js
file.
Add the following imports to the top of Square.jsx:
// Square.jsx
import { useRef } from "react"
import { random } from "./util"
Declare a useRef
variable at the top inside of the Square component.
// Square.jsx
// export const Square = ({ idx, ...
const theSquare = useRef(null)
Connect our ref to the <div>
in the return JSX by adding the ref=
attribute:
// Square.jsx
// <div
// className="square"
// onClick={...
ref={theSquare}
// >
Next, we're going to add a function to handle the mouseOver
event and pass it as an attribute of the <div>
in the return JSX.
Add this function to the Square component, after the useRef
hook and before the return statement:
// Square.jsx
// const theSquare = ...
function handleMouseOver(event) {
const newColor = `rgba(${random(255)}, ${random(255)}, ${random(
255
)}, .65)`
theSquare.current.style.background = newColor
}
// return ( ...
Here we are using the random
utility function to assist in generating a randomized RGBA color. The syntax may look a little funny if you aren't familiar with Template Literals, but it isn't important for this demonstration.
Add this function as an event attribute to our <div>
:
// Square.jsx
// <div
// ...
onMouseOver={handleMouseOver}
// >
Finally, we'll add an inline event handler to reset the background color when the mouse leaves the square.
Add this event handler as another attribute of the div opening tag:
// Square.jsx
// <div
// ...
onMouseOut={() => (theSquare.current.style.background = "transparent")}
// >
Enjoy the lights!
The final extra add-on for this application is going to change the border style of the squares every time a new game starts. To do this, we're going to use another useEffect
hook, but it will be declared a little differently than the first one. Remember that the useEffect
hook can be passed an array of dependencies that will cause the callback function to re-fire.
Since we're utilizing that random
helper function from before, we'll need to import that to our App.jsx
as well.
Add the random
import to the top of the App.jsx
file:
// App.jsx
import { random } from "./util"
Add this hook to the App component just after the existing useEffect
hook that we already have:
// App.jsx
// useEffect() => {
// ...
// })
useEffect(() => {
if (!gameOver) {
const allSquares = document.getElementsByClassName("square")
const borders = [
"hidden",
"dotted",
"dashed",
"solid",
"double",
"groove",
"ridge",
]
const newBorder = borders[random(borders.length) - 1]
for (const sq of allSquares) {
sq.style.borderStyle = newBorder
}
}
}, [gameOver])
Note that this may not be best-practice for manipulating the DOM in React, but will work for our demonstration purposes here
So, what we are doing here is waiting for our gameOver
dependency supplied in the second parameter of the hook to change, which happens when the app first launches (because of the initial useState
declaration), when the game is over, and when the "RESET" button is clicked after the game is over. If, after any of these actions, gameOver == false
, we pick a random border style from the supplied list and change the style of all of the squares to match. The most important thing to note here is how the dependency array ties in with the callback function, both supplied to the useEffect
hook.
Your app should now have some fancy border styles and color highlights on hover!
If you'd like to practice further by applying React concepts on your own, try making further modifications and adding additional features to this application. You won't be provided the code for these and they may require additional research, but generous hints will be provided if needed. Good luck!
We want to display any Square containing an X with one color, and Squares containing O with another. For example, red and blue. Bonus points if the user can change the colors themselves.
See Hints
-
Square.jsx
can read its ownvalue
property and use that to set a custom CSS class or Reactstyle
object. - An HTML color input sets an inner value of a hex color string and fires the
onChange
event. - You may need to pass an additional properties to the
Square
component.
We want to keep a tally of how many games each player wins and display a simple scoreboard on screen. Bonus points if the scoreboard persists between refreshes (or closing/opening the browser) and includes functionality to reset.
See Hints
- Think about React hooks and the best way to update a stateful value in this context
- The
localStorage
API allows for simple key-value pair storage in the user's browser. - How can we make sure we are only loading from
localStorage
(or wherever the data persists) once, upon initial page load? - Reset functionality already exists in this application for other data and works very similarly. Take inspiration!
You can test your knowledge following the concepts in this training module by taking the following assessment:
React Training Module Assessment
You do not need to be signed in to take this quiz. Score and correct answers will be shown upon completion.