This series will cover the use of React Hooks, starting with useState, and will include the following:
- useState
- useEffect
- useContext
- useReducer
- useCallback
- useMemo
- useRef
- custom hooks
Mastering the React Hooks API will help you better use it in your work and get to the next level of mastering React. This series uses a lot of sample code and effects that are easy to use for beginners and review.
If you know how to write React Class, you need to know how to write setState() and props.
Let’s start with the first example.
Simple example of a usestate-counter
Click the +1 counter
Class component
CounterClass.tsx
import React, { Component } from 'react'
class CounterClass extends Component {
state = {
count: 0
}
incrementCount = () = > {
this.setState({
count: this.state.count + 1})}render() {
const { count } = this.state
return (
<div>
<button onClick={this.incrementCount}>Count {count}</button>
</div>)}}export default CounterClass
Copy the code
App.tsx
import React from 'react'
import './App.css'
import CounterClass from './components/CounterClass'
const App = () = > {
return (
<div className="App">
<CounterClass />
</div>)}export default App
Copy the code
Results the following
Creating such a counter is a simple three-step process
- Create a Class component
- Create the state
- Create increment method
How to use Function Component and State Hook implementation
The State Hook to achieve
HookCounter.tsx
import React, { useState } from 'react'
function HookCounter() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={()= > {
setCount(count + 1)
}}>Count {count}</button>
</div>)}export default HookCounter
Copy the code
App.tsx
import React from 'react'
import './App.css'
// import CounterClass from './components/1CounterClass'
import HookCounter from './components/1HookCounter'
const App = () = > {
return (
<div className="App">
<HookCounter />
</div>)}export default App
Copy the code
The effect is the same as the Class component
Let’s look at how to use useState
const [state, setState] = useState(initialState)
Copy the code
This line of code returns a state and a function to update the state.
During initial rendering, the state returned is the same as the value of the first parameter passed in.
The setState function is used to update state. It receives a new state value and enqueues a rerendering of the component.
Summarize the rules of use of Hooks
- Hooks can only be invoked at the top scope
- Cannot be used in inner loops, conditional judgments, or nested methods
- You can only use Hooks in React Function
- You cannot use Hooks in other normal functions
useState with Previous State
This section describes how to use previous State, which is used when your state value depends on a previous state value.
Again with the Counter example, add a button click +1 or -1
Counter sample
HookCounter.tsx
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
return (
<div>
Count: {count}
<button onClick={()= > {
setCount(initialCount)
}}>Reset</button>
<button onClick={()= > {
setCount(count + 1)
}}> + 1 </button>
<button onClick={()= > {
setCount(count - 1)
}}> - 1 </button>
</div>)}export default HookCounter
Copy the code
App.tsx
import React from 'react'
import './App.css'
import HookCounter from './components/3HookCounter'
const App = () = > {
return (
<div className="App">
<HookCounter />
</div>)}export default App
Copy the code
The effect is as follows:
It looks fine, but it’s not safe to write like this! This is not the correct way to change a counter. Here’s why:
Again for example, add another button to the above example, adding 5 at a time
The following code
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
+ const increment5 = () => {
+ for (let i = 0; i < 5; i++) {
+ setCount(count + 1)
+}
+}
return (
<div>
Count: {count}
<button onClick={() => {
setCount(initialCount)
}}>Reset</button>
<button onClick={() => {
setCount(count + 1)
}}> + 1 </button>
<button onClick={() => {
setCount(count - 1)
}}> - 1 </button>
+
</div>
)
}
export default HookCounter
Copy the code
When I hit + 5, I only added 1
This is because the setCount method is asynchronous and cannot react and update immediately, and the count in the instantaneously activated multiple entries is still the old value and has not been updated.
Modify the code as follows:
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const increment5 = () => {
for (let i = 0; i < 5; i++) {
+ setCount(prevCount => prevCount + 1)
}
}
return (
<div>
Count: {count}
<button onClick={() => {
setCount(initialCount)
}}>Reset</button>
<button onClick={() => {
setCount(count + 1)
}}> + 1 </button>
<button onClick={() => {
setCount(count - 1)
}}> - 1 </button>
<button onClick={increment5}> + 5 </button>
</div>
)
}
export default HookCounter
Copy the code
If you want to use the previous state, you need to pass the value in function and return the new value after change. Modify the + 1-1 function as well. The code after improvement is as follows:
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const increment5 = () = > {
for (let i = 0; i < 5; i++) {
setCount(prevCount= > prevCount + 1)}}return (
<div>
Count: {count}
<button onClick={()= > {
setCount(initialCount)
}}>Reset</button>
<button onClick={()= > {
setCount(prevCount => prevCount + 1)
}}> + 1 </button>
<button onClick={()= > {
setCount(prevCount => prevCount - 1)
}}> - 1 </button>
<button onClick={increment5}> + 5 </button>
</div>)}export default HookCounter
Copy the code
summary
When previousState is used, it is passed to the setState method using the setter function. To ensure that the correct previous state is obtained.
In the re-render, the first value returned by useState will always be the latest updated state.
useState with Object
There are some caveat to calling setState when the state in useState is an object. UseState does not automatically merge the updated object. You can do this using the functional setState combined with the expansion operator.
Wrong example firstName & lastName
HookCounter.tsx
import React, { useState } from 'react'
function HookCounter() {
const [name, setName] = useState({
firstName: ' '.lastName: ' '
})
return (
<form>
<input
type="text"
value={name.firstName}
onChange={e= > {
setName({
firstName: e.target.value
})
}}
/>
<input
type="text"
value={name.lastName}
onChange={e= > {
setName({
lastName: e.target.value
})
}}
/>
<h2>Your first name is {name.firstName}</h2>
<h2>Your last name is {name.lastName}</h2>
</form>)}export default HookCounter
Copy the code
Notice that onChange on the input tag, each time you setName, only one property in the object is being operated on. The lastName property disappears when only firstName is assigned, which is a wrong way to write it.
Since I’m using TSX to write components, my compiler is reporting an error:
The browser also directly reported an error
Correct example – Manually merge objects
The expansion operator is used here to solve the problem of this object
import React, { useState } from 'react'
function HookCounter() {
const [name, setName] = useState({
firstName: ' '.lastName: ' '
})
return (
<form>
<input
type="text"
value={name.firstName}
onChange={e= >{ setName({ ... name, firstName: e.target.value }) }} /><input
type="text"
value={name.lastName}
onChange={e= >{ setName({ ... name, lastName: e.target.value }) }} /><h2>Your first name is {name.firstName}</h2>
<h2>Your last name is {name.lastName}</h2>
<h2>{JSON.stringify(name)}</h2>
</form>)}export default HookCounter
Copy the code
summary
When the object is operated in state Hook, the attributes in the object will not be automatically merged. We need to manually merge the objects, and we can use the expansion operator.
So, arrays are similar, see the next section.
useState with Array
The list of the sample
Click the button to add a random number 1-10 to the list
UseStateWithArray.tsx
import React, { useState } from 'react'
interface ItemType {
id: number
value: number
}
function UseStateWithArray() {
const [items, setItems] = useState<ItemType[]>([])
const addItem = () = > {
setItems([
...items,
{
id: items.length,
value: Math.ceil(Math.random() * 10)})}return (
<div>
<button onClick={addItem}>add a number</button>
<ul>
{
items.length > 0 && items.map((item: ItemType) => (
<li key={item.id}>{item.value}</li>))}</ul>
</div>)}export default UseStateWithArray
Copy the code
The effect is as follows:
Note how typeScript is used in the hooks, as shown in the following article
- Use React hooks in TypeScript
UseState summary
So much for the use of useState, here is a little summary.
- You can use state in functional components
- In a class component, state is an object, but in useState, state can be anything but an object
- UseState returns an array of two elements
- The first is the current value of state
- The second is the setter method for state, which triggers Rerender when called
- If the current state depends on the previous state, you can pass a function in the setter for state that takes the previous state and returns the new state
- For objects and arrays, note that old variables are not automatically completed in state; you need to manually replenish them yourself using the expansion operator
This is the end of useState. In the next article, we will learn the use of useEffect.