React hooks
August 21, 2021
Content |
---|
UseState |
UseEffect |
In a distant past, maybe a year ago or so, if you had a functional component and, all of a sudden realised, that you needed access to your state components or lifecycle methods, you had to convert them into a class component first.
That's no longer the case with hooks. A React hook is a function that makes it possible to work with other React features like state and lifecycle methods. UseState
is one of React's built-in hooks, and it does just that, it brings class components state functionality into React function components.
React state in a class
The state is what determines how the component behaves and renders. In a class the state is always an object. This is not the case with hooks. The state can be a string, number, boolean or an object with each piece of state holding a single value.
{
count: 0,
name: '',
loading: false
}
A class component state is user-defined, and always a JS object. If some value isn't used for rendering or data flow, it shoudn't be included
UseState hook
Assuming we have a stateless functional component like this one below rendered in the screen.
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => (
<div>
<p>App</p>
</div>
)
}
ReactDOM.render(<App />,
document.getElementById('root'));
Adding useState
Import useState from React, set the state vars and initial value to zero — in a class, this will be the object's property type number value; but here it can just be a primitive data type number value. What you should expect to get back from calling 'useState' is an array with two items or values: the current state value, and a function we can call to update the state.
The example below assumes it is a counter application, and I have set useState initial count value to zero. Probably it's worth pointing out that 'count' as one of the two items returned to the array, it could have only been accessed by passing its index value. But since I have destructured the array already, and set count at position zero, I no longer have to.
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const App = (props) => {
const [count] = useState(props.count);
return (
<div>
<p>Count is ${count}</p>
</div>
)
}
}
A functional component with React hooks. UseState set, value accessed and rendered in the screen.
Adding button to the component
Building upon the previous component, I now add a button
and wire up an onClick event listener that will increase the count value by 1.
Then, I access the second item returned by useState; which is also destructured and name it setCount.
The onClick event doesn't have to be a standalone function like the one I have defined for the Plus One
button. It can also be set inline, similar to what I did for the Minus One
button, instead of passing a function call to the onClick event.
const App = (props) => {
const [
count, setCount
] = useState(props.count);
const handlePlus = () => {
setCount(count + 1);
}
return (
<div>
<p>Count is ${count}</p>
<button
onClick={handlePlus}
>Plus One</button>
<button
onClick={() =>
setCount(count - 1))
}
>
Minus One
</button>
</div>
)
}
App.defaultProps = {
count: 0
}
Adding more state
Building upon previous component. We're already tracking count. Now, I'll be adding a 'controlled' input box and to see its value as it changes, and I'll render it right next to count.
We're already tracking count — still a primitive type number. This input box's state is completely independent of count - unlike classes where all the state is managed by a single object.
I've added an onChange event that reacts to input value changes. Unlike classes where the old state is merged with new state, in hooks the new state simply takes the place of the old one. I've also added an onClick event, and this doesn't have to be a standalone function like I've done wiht onChange where I declared a function. You can just set it inline, as shown below with Minus One button.
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const App = (props) => {
const [
count, setCount
] = useState(props.count);
const [
text, setText
] = useState('');
const handlePlus = () => {
setCount(count + 1);
}
return (
<div>
<p>{text || 'count'}
is ${count}
</p>
<button
onClick={handlePlus}
>Plus One
</button>
<button
onClick={() =>
setCount(count - 1)
)}
>Minus One
</button>
<input type="text"
value={text}
onChange={(e) =>
setText(e.target.value)
}
/>
</div>
)
}
ReactDOM.render(<App count={0} />,
document.getElementById('root'));
UseState with an array of objects
Example below, a functional component with 'useState' and the state keeping track of changes is an object's array.
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const NoteApp = () => {
const [notes, setNote] = useState([]);
const [title,setTitle] = useState('');
const addNote = (e) => {
e.preventDefault();
setNote([
...notes,
{
title
}
])
//clear form
setTitle('');
} //addNote
const removeNote = (noteObj) => {
let title = noteObj.title;
setNotes(notes.filter(itm =>
itm.title !== title
))
}
return (
<div>
<h1>Notes</h1>
{
notes.map(itm => {
<div ket={itmm.title}>
<h3>{itm.title}</h3>
<button
onClick={(itm) =>
removeNote(itm)}>Delete
</button>
</div>
})
}
<p>Add note</p>
<form onSubmit={addNote}>
<input type="text"
onChange={() =>
setTitle(e.target.value)}
/>
<button>Submit</button>
</form>
</div>
)
}
Effect hook
Another built-in hook. React describes it as "componentDidMount, componentDidUpdate, and componentWillUnmount combined".
How-to work with effect
Type useEffect
and pass a function to it.
As shown below, it runs once when the component first mounts, and every time thereafter the state changes.
//hook
useEffect()
//pass a function to hook
useEffect( + () => {})
//placed inside function body
use(effect() => {
console.log(
'running effect'
);
})
Sync state with effect
UseEffect
allow us to easily sync our application's state with other parts of the application.
In the example below, I'm tracking multiple pieces of state. The input box value and text in the paragraph above it are being tracked with the text var, therefore they must be rendered simultaneously, and by introducing useEffect, I can
make sure they're both updated at the same time.
Similarly, I could with Effect, for example leave out any dependencies that aren't needed or maybe negatively impacting the application's performance by unnecessarily calling state too many times. Something you cannot do with classes. At least not very straight-forward.
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const App = props => {
const [
count, setCount
] = useState(props.count);
const [text, setText] = useState('');
useEffect(() => {
console.log('use effect ran');
//header
document.body
.getElementsByTagName('header')[0]
.textContent = text;
},[text])
return (
<div>
<p>The current {text || 'count'}
is {count}
</p>
<button onClick={() =>
setCount(count -1)}>
Minus One
</button>
<button onClick={() =>
setCount(props.count)}>
Reset
</button>
<button onClick={() =>
setCount(count +1)}>
Plus One
</button>
<input type="text" onChange={() =>
setText(e.target.value)
)}
/>
</div>
)
}
Example above A functional component with two pieces of state: count and text. To set the initial value of header and keep its state updated, I added
text
to the effect hook's array. It tells React to runcomponentDidUpdate
with each state update.
Effect hook & empty array
Passing an empty array as second argument to the effect hook mimics componentDidMount
lifecycle method behaviour.
to be continued...[]