My React.js Notes from Linkedin Learning – React Essential Training
The following are my notes from React.js Essential Training from LinkedIn Learning. They are automatically fetched from this file in my GitHub repo. Please see that repo for my notes from other React courses such as this one from Udemy.
4. React State in the Component Tree
8. React Testing and Deployment
Chrome Extension: React Developer Tools
Quick React Sandbox: React.new
React.js-Essential-Training from Linkedin Learning
- We could just add the React scripts:
<script
src="https://unpkg.com/react@17/umd/react.development.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
crossorigin
></script>
- Add a div#root where you want react to render
<div id="root"></div>
- Then manually render the elements:
<script type="text/javascript">
// We pass two parameters:
// 1. The element we want to render
// 2. Where to put it
ReactDOM.render(
React.createElement("h1", null, "Hello world!"),
document.getElementById("root")
);
</script>
</body>
In the code above, we can actually move the parameter to be rendered into its own variable.
let heading = React.createElement(
"h1",
{ style: { color: blue } },
"Hello world!"
);
ReactDOM.render(heading, document.getElementById("root"));
We could write the element to be rendered as jsx, but this wouldn't run in the browser:
ReactDOM.render(
<ul>
<li>π€</li>
<li>π€ </li>
<li>π</li>
</ul>,
document.getElementById("root")
);
Instead, we can use Babel to convert it into a format compatible with all browsers.
All we need to use Babel is to:
- Add its script,
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
- Change the script type where jsx is used.
<script type="text/babel">
Babel all scripts with type "text/babel" and convert the JSX in them into createElement calls so that we don't have to write like this:
ReactDOM.render(
/*#__PURE__*/ React.createElement(
"ul",
null,
/*#__PURE__*/ React.createElement("li", null, "\\uD83E\\uDD16"),
/*#__PURE__*/ React.createElement("li", null, "\\uD83E\\uDD20"),
/*#__PURE__*/ React.createElement("li", null, "\\uD83C\\uDF1D")
),
document.getElementById("root")
);
This is not how you would do it in production.
We can inject dynamic content in between JSX tags using {}
syntax and typing objects, variables, or props in them.
let robot = "π€";
let cowboy = "π€ ";
let moon = "π";
let name = "React";
ReactDOM.render(
<ul>
<li>{robot}</li>
<li>{cowboy}</li>
<li>{moon}</li>
<li>{name.toUpperCase()}</li>
<li>{name.length}</li>
</ul>,
document.getElementById("root")
);
A component is just a JS function that returns some JSX.
Rule #1: Component function names start with a capital letter, which helps distinguish <Header>
component from the built in <header>
dom element.
So instead of putting all our JSX in the render method like this:
ReactDOM.render(
<ul>
<li>π€</li>
<li>π€ </li>
</ul>,
document.getElementById("root")
);
We can just put all that into a component, let's call it App:
function App() {
return (
<ul>
<li>π€</li>
<li>π€ </li>
</ul>
);
}
... and replace them with a JSX tag that refers to that component function:
ReactDOM.render(<App />, document.getElementById("root"));
In general, you run ReactDOM.render for one parent component and the other components are nested in it.
function App() {
return (
<div>
<Header />
<Main />
</div>
);
}
Rule #2: The component must return a single tag. You can either wrap everything in a div or use React Fragments: wrapping everything in <></>
:
function App() {
return (
<>
<p>My emojis:</p>
<ul>
<li>π€</li>
<li>π€ </li>
</ul>
</>
);
}
You can build your tree from there by nesting components inside the App component.
Step 1: Add an argument called props to the component function.
function Header(props) { ...
Step 2: When we add an attribute in the JSX where we call this component function, it will pass that attribute as a prop to the component function.
<header name="Cansin" />
Step 3: Now you can use that passed arg in the component function. If you are putting it in jsx, you'd need curly brackets.
<h1>{props.name}'s Kitchen</h1>
If we pass a number, we'd pass it between braces:
<footer year="{2024}" />
or we could pass JS as a prop:
<footer year="{new" Date().getFullYear()} />
If you want to render a list from a passed as an Array:
<ul>
{props.dishes.map((dish) => (
<li>{dish}</li>
))}
</ul>
But this gives a warning in console: Warning: Each child in a list should have a unique "key" prop.
You could fix it like this but it is not recommended:
{
props.dishes.map((dish, i) => <li key={i}>{dish}</li>);
}
Instead, we can create an object for each item in the Array, then pass that Array of Objects as the prop.
const dishObjects = dishes.map((dish, i) => ({
id: i,
title: dish,
}));
<main adjective="amazing" dishes="{dishObjects}" />
We'd also need to update the object mapping:
<ul>
{props.dishes.map((dish, i) => (
<li key={i}>{dish}</li>
))}
</ul>
We have () around {} because an arrow function can't return an object without wrapping it in ().
src/index.js
is like your index.html
, it is the entry point to your app.
Components are in their own files, they export default
the component function at the end.
You run it with npm start
, you may need to run npm install
first to install the dependencies from package.json.
Instead of using props as the arg for the component function, you can use an object with the keys of the elements from the props:
function App({ cheer, library }) {
return (
<div className="App">
<h1>
{cheer} from {library}.
</h1>
</div>
);
}
Instead of function App(props)
and {props.cheer} from {props.library}
.
You can desctructure an Array and assign its elements to variables as below:
const [firstCity, second] = ["Tokyo", "Tahoe City", "Bend"];
Then you can use those variables in a component like <p>{firstCity} and {second}.</p>
.
This is used in useState.
Step 1: Import useState in the component.
import { useState } from "react";
Step 2: Define a variable to hold the state and a function to update it.
If we assign useState()
; to a variable and log it in console, we see that it is an Array with an undefined value (value of the state) and a function (to update the value). So we need to destructure useState()
into 2 variables.
const [emotion, setEmotion] = useState("happy");
If you want to give it an initial value, you can pass it in useState() as an arg.
-
When the app is first rendered, emotion = 'happy', you can print it wherever with
<h1>Hello from {emotion} guy!</h1>
-
We can update it with the setEmotion method:
<button onClick={() => setEmotion("sad")} <!-- or --> <button onClick={toggleEmotion}> <!-- where toggleEmotion() is a function-->
Used for side effects that aren't related to a component's render. If all you want to do when a state changes is to update a value in dom, you can just put that value in the component, but it you want to do something else, you can pass that function.
Step 1: We start by importing it:
import { useState, useEffect } from "react";
Step 2: We call it while passing in 2 args:
- A function that determines what it does,
- An array, which determines when it does it.
- If we pass an empty array
[]
it only does once when rendering and never again. - If we pass in a property or a state value, it listens to its changes.
- If we pass nothing, it is run every time the component is (re-)rendered.
- If we pass an empty array
useEffect(() => {
console.log(`It's ${emotion} now.`);
}, [emotion]);
Allows you to take the function that defines how the state is updated in a single place.
Takes 2 args:
- Function we'll use to update our state
- Initial state
With useState:
import { useState } from "react";
function App() {
const [checked, setChecked] = useState(false);
return (
<div className="App">
<input
type="checkbox"
value={checked}
onChange={() => setChecked((checked) => !checked)}
id="chx"
/>
<label htmlFor="chx">{checked ? "checked" : "not checked"}</label>
</div>
);
}
vs with useReducer:
import { useReducer } from "react";
function App() {
const [checked, setChecked] = useReducer((checked) => !checked, false);
return (
<div className="App">
<input
type="checkbox"
value={checked}
onChange={setChecked}
id="chx"
/>
<label htmlFor="chx">{checked ? "checked" : "not checked"}</label>
</div>
);
}
export default App;
Anytime you are using useRef, you are managing form components outside of the state.
You can disable the default form submit with <form action="" onSubmit={submit}>
which is defined as:
const submit = (e) => {
e.preventDefault();
};
You can use useRef hook to get the value of an element in dom. Import it:
import { useRef } from "react";
Then you can get the value of a form input with:
-
Define a variable with useRef();
const textTitle = useRef();
-
Assign this variable as a reference to the input
<input ref={textTitle} type="text" placeholder="Color title" />
-
Then you can access it with:
const title = textTitle.current.value; console.log(title);
-
You can also set them with
current.value
. E.g. for reseting after submissiontextTitle.current.value = "";
So managing this form with uncontrolled components using useRef would be:
import "./App.css";
import { useRef } from "react";
function App() {
const colorTitle = useRef("");
const hexColor = useRef("#000000");
const submit = (e) => {
// Prevent the form from submitting
e.preventDefault();
// Do something with the inputs
console.log(`${colorTitle.current.value}, ${hexColor.current.value}`);
// Clear the form
colorTitle.current.value = "";
hexColor.current.value = "#000000";
};
return (
<form onSubmit={submit}>
<input ref={colorTitle} type="text" placeholder="color title..." />
<input ref={hexColor} type="color" />
<button>ADD</button>
</form>
);
}
export default App;
Unlike useState, where the component will re-render if there is a change, useRef will not re-render. Anytime you see useRef, we are creating an uncontrolled component.
Contolled means we are creating state values for the form inputs:
-
Our variables would be the state values,
-
We'd replace ref attributes of the inputs with value attributes that point to states,
-
We'd need to listen to onChange event of the inputs and call the state change functions onChange:
<input onChange={(event) => setTitle(event.target.value)} value={title} type="text" placeholder="Color title" />
-
How we clear the form would also change, you don't just have variables to assign values like useRef, you need to call state update function.
setTitle(""); setColor("#000000");
Managing the form with useState:
import "./App.css";
import { useState } from "react";
function App() {
const [colorTitle, setColorTitle] = useState("");
const [hexColor, setHexColor] = useState("#000000");
const submit = (e) => {
// Prevent the form from submitting
e.preventDefault();
// Do something with the inputs
console.log(`${colorTitle}, ${hexColor}`);
// Clear the form
setColorTitle("");
setHexColor("#000000");
};
return (
<form onSubmit={submit}>
<input
onChange={(event) => setColorTitle(event.target.value)}
type="text"
value={colorTitle}
placeholder="color title..."
/>
<input
onChange={(event) => setHexColor(event.target.value)}
type="color"
value={hexColor}
/>
<button>ADD</button>
</form>
);
}
export default App;
The difference between the two approach is:
- With useRef, we are reading the input values and updating our variables only when the form is submitted,
- Steps:
- Create const values with useRef('')
const colorTitle = useRef("");
- Tie it to the input value with ref attribute
<input ref={colorTitle} type="text" />
- Read it with current.value
colorTitle.current.value;
- Change input value with current.value
colorTitle.current.value = "";
- Create const values with useRef('')
- Steps:
- With useState, we are continuously listening to onChange event of the input fields and updating our variables.
- Steps:
- Create const values with useState(''):
const [colorTitle, setColorTitle] = useState("");
- Listen onChange, grab the event and assign event.target.value to the state:
<input onChange={(event) => setColorTitle(event.target.value)} type="text" />
- Read it with state name
colorTitle;
- Change input value with setter function
setColorTitle("");
- To make the input value update, you also need to tie its value to the state name.
<input onChange={(event) => setColorTitle(event.target.value)} type="text" value={colorTitle} />
- Create const values with useState(''):
- Steps:
A custom hook is a function, its name always starts with the word "use".
When you have a repeatable behavior in your code, you can make it a custom hook.
import "./App.css";
import { useState } from "react";
function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
return [
{
value,
onChange: (e) => setValue(e.target.value),
},
() => setValue(initialValue),
];
}
function App() {
const [titleProps, resetTitle] = useInput("");
const [colorProps, resetColor] = useState("#000000");
const submit = (e) => {
e.preventDefault();
alert(`${titleProps.value}, ${colorProps.value}`);
resetTitle();
resetColor();
};
return (
<form onSubmit={submit}>
<input {...titleProps} type="text" placeholder="color title..." />
<input {...colorProps} type="color" />
<button>ADD</button>
</form>
);
}
export default App;
They can help with validation, performance optimiziation (reducing re-renders); they abstract away the state management logic...
- Formik
- React Hook Form
- useState: returns a variable to store data and a setter function.
- When tieing a state variable to an input, you need to set up sync in both ways:
- Listen to onChange and set event.target.value as the new value, to update state from input,
- Set the value attribute of the input equal as the state variable, to update dom from the state varaible.
- When tieing a state variable to an input, you need to set up sync in both ways:
- useRef: Gives you a variable, you assign it to an input with ref attribute, then you can read its current.value.
- useEffect: Allow you to do things when something's value changes.
- useReducer: Left at this and then custom hooks.
Custom hook recipes:
We can have a state to keep the data:
const [data, setData] = useState(null);
Then use the useEffect call the API:
useEffect(() => {
fetch(
`https://api.github.com/users/moonhighway`
)
.then((response) => response.json())
.then(data => setData(data))
}, []); // we don't want any dependencies since we only want to fetch when our app first rendered
When fetching data, there are 3 possible states:
- Loading,
- Success,
- Error.
We can have 3 states for these:
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(null);
Before fetch, we can set loading true, after fetched we can set it to false again. Lastly, we can chain a catch at the end:
useEffect(() => {
setLoading(true);
fetch(`https://api.github.com/users/cansinacarer`)
.then((response) => response.json())
.then(setData)
.then(() => setLoading(false)
.catch(setError));
}, []);
Then we can return different things based on these states:
if (loading) return <h1>Loading...</h1>;
if (error) return <pre>{JSON.stringify(error)}</pre>;
if (!data) return null;
return (
<GithubUser
name={data.name}
location={data.location}
avatar={data.avatar_url}
/>
);
You import these:
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
and use them as below, this using:
- Link component for your links
- Routes and Route to define the paths,
- BrowserRouter (imported as Router below) to wrap everything.
<Router>
<div className="App">
<header className="App-header">
<nav>
<ul>
<li>
<Link to="/" draggable={false}>Home</Link>
</li>
<li>
<Link to="/about" draggable={false}>About</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/about" element={<About />} />
<Route path="/" element={<Home />} />
</Routes>
</header>
</div>
</Router>
Say we want to have a about/history page from the component below.
export function History() {
return(
<div>
<h1>Our History</h1>
</div>
)
}
-
We'd put another Route for history inside the About Route: Note that you don't put a slash in the path for
history
<Route path="/about" element={<About />}> <Route path="history" element={<History />} /> </Route>
-
We'd also need to decide where in the About component will the child component be displayed. For this, we import and use the Outlet component. We place it where we want the Child to be rendered.
import {Link, Outlet} from "react-router-dom"; // ... export function About() { return ( <> <nav> // ... </nav> <h1>About</h1> <Outlet /> </h1> ) }
Create-react-app comes with Jest for testing. When you run npm test
, it runs your tests.
This will run the tests you place in *.test.js
files.
- Import the function you want to test:
import {timesTwo} from "./functions";
- Use the
test()
,expect()
, andtoBe()
, which takes args for a name and a function as below.test("Multiplies by two", () => { expect(timesTwo(4).toBe(8)); });
There are many other "matchers" like toBe(), see the full list. E.g:
expect(a + b).not.toBe(0);
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
expect(value).toEqual(4);
You can also check whether the components render as expected, without manually looking at them, using @testing-library
.
import {render} from "@testing-library/react";
import { Star } from "./Star"; // importing the component to be tested
test("renders an h1", () => {
const {getByText} = render(<Star />); // query
const h1 = getByText(/Cool Star/); // expected content
expect(h1).toHaveTextContent("Cool Star");
});
Here the render
function takes a React component, in this case <Star />
, and returns an object with various methods and properties that allow us to interact with the rendered component in our tests. The object returned by render
is being destructured, and we are specifically extracting the getByText
method from it. The getByText
method is used to query the rendered component and find an element that contains the specified text. It uses a regular expression (/Cool Star/)
to match the expected content.
The next line, const h1 = getByText(/Cool Star/);
, is using the getByText method to find an element within the rendered component that contains the text "Cool Star". The result of this query is being assigned to the variable h1.
Say we want to test clicking on this checkbox and seeing if the checked
value changes.
import { useReducer } from "react";
export function Checkbox() {
const [checked, setChecked] = useReducer(
checked = !checked, // return whatever the opposite of checked is
false // initial value
);
return (
<>
<label htmlFor="checkbox">
{checked ? "checked" : "not checked"}
</label>
<input type="checkbox" id="checkbox" value={checked} onChange={setChecked} />
</>
);
}
We fire click event and test:
import {render, fireEvent} from "@testing-library/react";
test("Selecting the checkbox should change the value of checked to true.", () => {
const {getByLabelText} = render(<Checkbox />);
const checkbox = getByLabelText(/not checked/i); // i prevents case sensitivity
// Fire the event of clicking on this checkbox
fireEvent.click(checkbox);
// Our expectation after click
expect(checkbox.checked).toEqual(true)
});
You can either connect your repo, or upload a built version of your app. You can build it with npm run build
, then upload the generated build folder.