10.2 Conditional Rendering, Data, and State
# Components From Data
We have already discussed the concept of state-driven UIs. So, let's look next at how we can generate content in React from Data.
Starting with the example from last week:
//this file is /src/components/List/index.js
import ListItem from '../ListItem/index.js';
function UserList() {
const users = ['Sheldon', 'Georgie', 'MeeMaw', 'Missy'];
return (
<ul>
<ListItem key="Sheldon" name="Sheldon" />
<ListItem key="Georgie" name="Georgie" />
<ListItem key="MeeMaw" name="MeeMaw" />
<ListItem key="Missy" name="Missy" />
</ul>
);
}
export default UserList;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In this example we are hard coding four <ListItem/> components because there are 4 items in the users array. Instead, we should use an Array map method.
//this file is /src/components/List/index.js
import ListItem from '../ListItem/index.js';
function UserList() {
const users = ['Sheldon', 'Georgie', 'MeeMaw', 'Missy'];
const items = users.map((name) => <ListItem name={name} key={name} />);
return <ul>{items}</ul>;
}
export default UserList;
2
3
4
5
6
7
8
9
10
Here, we are creating an array called items that will have four <ListItem> components, that each have name and key props with the values from the users array.
We could have put the users.map() inside the return statement, between the <ul></ul> JSX wrapped in {} as an expression. That would be valid too.
Keys are Required
When you have more than one React component of the same type that share the same parent element, then you must always use a key attribute so React can differentiate between them.
Each key must have a unique value.
This gives us components that are built from data. However, we still have no way to easily update the components if the data changes. That means we need state.
Guide to rendering lists in React (opens new window)
When we are writing vanilla JS, we are avoiding calling append(), appendChild() or innerHTML while inside a loop. We don't want to force the browser to redraw the page repeatedly.
React is doing this for us automatically. A function component has a return value which will be a block of JSX. That block of JSX is used to update the virtual DOM. At that point when the actual DOM is updated is in the hands of React and its internal process.
With React, we are never making direct updates to the actual DOM. We are building a virtual DOM tree with JSX. The return values from the component functions add to and update the virtual DOM tree. Updates to state values trigger calling component functions and updating the tree.
How React builds the virtual DOM tree (opens new window)
# Conditional Rendering
Conditional Rendering is how we can dynamically change whether components are rendered or if they are rendered with different data. This was mentioned already last week but it important enough to go into more detail.
Because we can embed expressions inside our JSX we can control the rendering process.
A few key things to remember:
- A function can have multiple
returnstatements and only the first one reached will run. - Curly braces are to denote expressions in JSX, so we need to avoid them in our returned expressions. For this reason, logical short-circuiting, ternary operators, and nullish operators are best.
- If you do use an
if elsestatement, then do it outside the return statement.
Here are a few samples:
function SomeComponent(props) {
//short-circuiting based on a prop value
//only one thing to render.
//deciding if you want to render it.
//typically used inside the return
return props.alive && <div>{props.name}</div>;
}
function SomeComponent(props) {
//ternary operator to choose which component to render
//choosing between two components or expressions
//used inside or outside the return
return props.alive ? <ComponentA /> : <ComponentB />;
}
function SomeComponent(props) {
//nullish assignment and then using the result
//usually used before the return
//handles props that are null or undefined
const name = props.name ?? 'Default name';
return <ComponentA name={name} />;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Components with State
When you create a component from a function it will be, by default, stateless. That just means it has no data that is being watched for updates and changes.
To create a state value to be watched, inside your component function, we use a hook function called useState. We call the function and pass it a default value for the state object. It returns
an array that contains the current value of the state object PLUS the method that you will call any time you want to change the state object.
We call the useState function from inside our component function.
The convention to use when naming the variable and update function is to use a descriptive word for the variable and the same word with set in front of it as the function name.
function MyComponent(props) {
//props are passed in from the parent component
let stateStuff = useState(props.name);
//stateStuff is an Array that holds the state variable and the state update function
const name = stateStuff[0];
const setName = stateStuff[1];
return {};
}
2
3
4
5
6
7
8
9
The above example will work. However, it has three lines of code that could be reduced to a single line if we use destructuring.
function MyComponent(props) {
const [name, setName] = useState(props.name);
//exact same result but in one line of code.
//passing the state value to a child component through props
return <ChildComponent name={name} />;
}
2
3
4
5
6
For each object or value that you want to put into state you will call useState.
If you have a child component that needs access to the value of the state variable then we use props to pass it down from parent to child.
Where to Place State Variables
When creating your state variables you can put them in every component, you can put them all in one component.
The key is to look at where the state data needs to be shared. Which components need the values? Then go to the first parent that will encompass all the components using that data and put the call to
useState there.
Remember, that with React, you are generally building a Single Page Application. State variables are client-side data objects that are used within the context of a single page to control what data
is displayed inside each component.
When a user navigates to a new page, the components will be replaced and rebuilt, and along with the new components will be new state variables and new values.
# State and Hooks
The whole purpose of us creating state variables instead of regular variables, is to let React update the interface any time the value of the variables changes.
We use the useState hook to both create the variable and the function that is to be used to change the value of the variable.
The last two versions of React have supported a feature called hooks. hooks get used with functional components to "hook" into the component lifecycle (as well as other React features). Components
are created and rendered and updated and removed. These steps and the acknowledgement of when those things are about to happen make up the component lifecycle. The old class-based components from
earlier versions of React actually had methods that you would add inside the classes that are named after the lifecycle steps.
In function-based components there are no lifecycle methods. Instead we use hooks.
The purpose of the component lifecycle is to monitor the values of state and prop values, insert the values into the interface, and update the interface when the values change.
To use any of the hooks, we need to import them.
import { useState, useEffect } from 'react';
# useState
The purpose of the useState hook is purely to create state variables and the function that you will use to update their value.
You can call useState as many times as you need. Each time you call it, you will be creating a new array containing a variable and a function.
import {useState} from 'react';
export default MyComponent(props){
const [name, setName] = useState(props.username);
const [email, setEmail] = useState(props.email);
}
2
3
4
5
6
When calling the useState method, there are different things that you can pass in as arguments.
If you pass in a primitive value, like in the example above, or an Array or Object, then this will become the default value of the variable returned to the destructured Array (name or email).
If you pass a function to the useState method then that function will be run only on the initial render of the state value for the interface.
const [key, setKey] = useState(() => {
let id = Math.random().toString(16).substring(2, 10);
return id;
//id is returned as the first value in the Array that useState returns
//the setKey method will be created by useState
});
2
3
4
5
6
# useEffect
The useEffect hook is used to run functions either on initial render of the component OR on intial render AND every update.
If you want to do something asynchronous like fetch or indexedDB or localStorage or cache then inside the useEffect hook is where you call these.
useState and useEffect are frequently used together. useState creates a state variable with a default value of null or an empty Array. Then useEffect will call the asynchronous method(s) to
get the actual value for the state variable, and finally call the set method to update the state variable.
import { useState, useEffect } from 'react';
export default function MyComponent(props) {
const [people, setPeople] = useState([]);
//create an empty array that will be filled with data later on
useEffect(() => {
//initiate a fetch to get the people data
fetch(someURL)
.then((response) => response.json())
.then((json) => {
setPeople(json.people);
})
.catch((err) => {
setPeople(['Unable to load people', err.message]);
});
return () => {
//if you return a function it will be run if/when the component is removed
//If the useEffect is called based on a dependency change,
// then it gets called using the old state value, before the useEffect function runs
};
}, []);
//having an empty array means run this function only on initial render
return (
<ul>
(people.length === 0) ? <li>No people</li> : people.map((person)=>{<li>{person}</li>})
</ul>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
The useEffect method has a function to run as its first argument.
There is also a second argument. If left empty, the useEffect method will call the function on initial render plus any time any state or prop value is changed.
If the second argument is an empty array, it means only call the useEffect method function on the initial render.
If you want the useEffect function to be called when specific prop or state values are updated then place them inside the second argument array.
useEffect(() => {
//function to run on initial render
//plus when the props.name variable,
//or the state variables key or num are changed
}, [props.name, key, num]);
2
3
4
5
# Component Classes
Just as a reminder, from 2015 - 2019, React Components were built with the JavaScript Class syntax. They relied on "lifecycle" events and methods to track when components were loaded, updated, or removed.
This approach still works but is considered out of date and no longer a best practice. You will, if you work on many existing React projects, likely encounter class-based React components. So, it is good to understand the differences between function and class-based components.
Avoid code samples that have:
import React, { Component } from 'react';
constructor(props){
super(props);
//this is where we find the passed in props
//and create state variables
}
componentDidMount(){
//runs once the component has rendered for the first time
}
componentDidUpdate(){
//change made to props or state triggers this
}
class MyComponent extends Component {
render() {
//the render function is like the return value from our Component function
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
The old component classes use the lifecycle methods componentDidMount, componentDidUpdate, and others. The function components use hooks instead of the lifecycle methods.
# What to do this week
TODO Things to do before next week.
- Read all the content from
Modules 10.1, 10.2, and 11.1. - Continue watching the React in 2021 Video Tutorial Playlist (opens new window)