Using Arrays in React
Often times we may want generate JSX for many elements of an array, the standard way of doing so is using the array.map method. Use the example below to see how.
const Component = () => {
// an array of dogs
const dogs = [
{ name: "Sparky", age: 5 },
{ name: "Spot", age: 5 },
{ name: "Ralph", age: 5 },
{ name: "Fido", age: 5 },
]
// map over the dogs array and create an array of JSX for each dog
const dogJSX = dogs.map(dog => {
// we return JSX for each dog in the array which we store in the dog variable, essentially we are looping over dog of dogs
return (
<div>
<h1>{dog.name}</h1>
<h2>{dog.age}</h2>
</div>
)
})
// the component returns JSX that uses the dogJSX array
return <div>{dogJSX}</div>
}
Iterating Over an Object in React
Using Objects.keys to generate an array of strings that are the keys of the objects properties. You can then map over the array to generate JSX for each property.
const Component = props => {
const Arthur = {
name: "Arthur Bernier",
age: "35",
email: "ceo@arthurbernierjr.com",
}
return Object.keys(Arthur).map((key, index) => {
return (
<h2>
{key}: {Arthur[key]}
</h2>
)
})
}
The useState Hook
The useState hook allows us to generate variables that are special, as updating them would trigger your component and its children to update.
First step is always importing the useState hook.
import { useState } from "react"
Inside the body of your component function you can then initiate a state variable. The name convention is "state" for the variable and "setState" for the function that updates the states value.
If I wanted to create state for a counter it would look like this.
// initiate counter at 0, setCounter let's me update counter
const [counter, setCounter] = useState(0)
So a simple counter component would look like this...
import { useState } from "react"
const Counter = props => {
// Declare the state
const [counter, setCounter] = useState(0)
// Function to add one to the state
const addOne = () => {
// sets counter to its current value + 1
setCounter(counter + 1)
}
// The h1 display the counter and button runs addOne function
return (
<div>
<h1>{counter}</h1>
<button onClick={addOne}>Click Me to Add One</button>
</div>
)
}
That's as simple as it gets. What happens when the button is clicked.
- setCounter is passed the current value + 1
- React then compares this new value to the old value of counter
- If they are the same, React does nothing (beware of references as values when it comes to objects and arrays)
- If they are different then React does updates its VirtualDOM based on a re-render of the component and its children
- It then compares the virtualDOM to the real browser DOM and only updates the places in which they differ.
The above process is why variables that are "State" are reactive, meaning the DOM will updates when the value updates. All other variables are not reactive and will not trigger updates when changed.
NOTE: If the state is an object or array, make sure you pass a new array or object and not just modify the old one. Objects and Arrays are references, so if you pass the old array with modified values the references will still be equal so there will be no update to the DOM.
Example...
Don't do this
// modify the existing state
state[0] = 6
// then setState as the existing state, triggering NO update
setState(state)
Do this
// create a unique copy of the array
const updatedState = [...state]
// modify the new array
updatedState[0] = 6
// set the State to the updatedArray, DOM will update
setState(updatedState)
The useEffect Hook
Here is our counter component from earlier with a console.log and second piece of state.
import { useState } from "react"
const Counter = props => {
// Declare the state
const [counter, setCounter] = useState(0)
// second piece of state
const [evenCounter, setEvenCounter] = useState(0)
console.log("I'm just a random log")
// Function to add one to the state
const addOne = () => {
// if counter is even before the update, update evenCounter
if (counter % 2 === 0) {
setEvenCounter(evenCounter + 1)
}
// sets counter to its current value + 1
setCounter(counter + 1)
}
// The h1 display the counter and button runs addOne function
return (
<div>
<h1>{counter}</h1>
<h1>{evenCounter}</h1>
<button onClick={addOne}>Click Me to Add One</button>
</div>
)
}
So right now this component displays both counter in its JSX
- when we click the button counter will always go up by 1
- if counter is even before it is increased, evenCounter will go
Any code in the function body will run again on every render of the component. The component will render with every change of state. So in this case if we keep clicking the button that console.log will run again and again.
What if we only want it to run when evenCounter changes.
This is where the useEffect hook comes into play. This hook is a function that takes two arguments:
- A function that will be run immediately when the component loads and anytime any value in the second argument changes
- An array of values, when they change the function will run again. Usually an empty array if you never want the function to run again.
import { useState, useEffect } from "react"
const Counter = props => {
// Declare the state
const [counter, setCounter] = useState(0)
// second piece of state
const [evenCounter, setEvenCounter] = useState(0)
//making sure console.log only runs on certain renders
useEffect(() => {
console.log("I'm just a random log")
}, [evenCounter])
// Function to add one to the state
const addOne = () => {
// if counter is even before the update, update evenCounter
if (counter % 2 === 0) {
setEvenCounter(evenCounter + 1)
}
// sets counter to its current value + 1
setCounter(counter + 1)
}
// The h1 display the counter and button runs addOne function
return (
<div>
<h1>{counter}</h1>
<h1>{evenCounter}</h1>
<button onClick={addOne}>Click Me to Add One</button>
</div>
)
}
So notice the useEffect receives a function that executes our log, and we also gave it an array with evenCounter in it. This means...
- The function will run once when the component is first loaded
- The function will run again only when evenCounter changes
useEffect is more regularly used for API calls. Usually you'll call the API, get the data then update state inside a useEffect to prevent an infinite loop from occurring.
useEffect(() => {
axios(URL).then(data => setState(data))
}, [])
Also if the function given to useEffect returns a function, the returned function will be run when the component is removed from the DOM useful for remove event listeners that may be left behind (not something that should come up often)
The useRef hook
Think of the useRef hook kind of like document.querySelector, it let's you assign a DOM node to a variable so you can access its properties. React declarative (express what you want, not how to make it) nature makes it hard to write normal imperative (how to make the thing step by step) DOM code. So if you need to get access to a DOM node like an input you can do the following:
import { useRef } from "react"
const Component = props => {
// create a new ref, we'll assign it in our JSX
const inputRef = useRef(null)
const handleClick = () => {
//log the inputs elements value
console.log(inputRef.current.value)
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Click Me</button>
</div>
)
}
Form Handling
There are two ways to handle forms in React.
- Controlled Forms: The value of the inputs are bound to state, so value of state and the value of the inputs are always in sync.
- Uncontrolled Forms: The forms are not bound by state, instead their values are pulled using a ref when needed.
Example of a Controlled Form
Parts:
- object holding form values as state
- handleChange function that updates the state when we type into the form
- handleSubmit function to handle form submission and do what you want with the data
import { useState } from "react"
const Form = props => {
//State to hold the form data
const [form, setForm] = useState({
name: "",
age: 0,
})
// handleChange function
const handleChange = event => {
// dynamically update the state using the event object
// this function always looks the same
setForm({ ...form, [event.target.name]: event.target.value })
}
const handleSubmit = event => {
// prevent page refresh
event.preventDefault()
// do what you want with the form data
console.log(form)
}
// The JSX for the form binding the functions and state to our inputs
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={form.name}
onChange={handleChange}
name="name"
placeholder="write name here"
/>
<input
type="number"
value={form.age}
onChange={handleChange}
name="age"
placeholder="write age here"
/>
<input type="submit" value="Submit Form" />
</form>
)
}
Example of an Uncontrolled Form
- a ref created for each input
- handleSubmit for when form is submitted
import { useRef } from "react"
const Form = props => {
// ref to get input values
const nameInput = useRef(null)
const ageInput = useRef(null)
const handleSubmit = event => {
// prevent page refresh
event.preventDefault()
// do what you want with the form data
console.log({
name: nameInput.current.value,
age: ageInput.current.value,
})
}
// The JSX for the form binding the functions and state to our inputs
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameInput} placeholder="write name here" />
<input type="number" ref={ageInput} placeholder="write age here" />
<input type="submit" value="Submit Form" />
</form>
)
}