Since the release of v16.8, hooks have been introduced to React. You may have already heard of hooks as a way to use common React features without writing a class-based component.
Hooks provide an interface for creating powerful functional components. They can be used to introduce state and manage side-effects. Note that this was previously not possible in functional components.
Prior to hooks, we relied on class-based components to use state and manage side-effects by using lifecycle methods. If you have used React, you have most likely written or encountered a class-based component.
Class-based components are still to be supported by React. But, you may want to know how to transform them into functional components using hooks. This guide will teach you how to do just that!
Class-based component (before)
Below is a typical example of a class-based component. It manages some internal state and uses lifecycle methods.
Counter
displays a count which can be incremented and decremented. Updating the name in the text field will also be reflected in the document title.
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Joe',
count: 0
}
this.updateName = this.updateName.bind(this)
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
componentDidMount() {
document.title = `${this.state.name}'s counter`
}
componentDidUpdate(prevProps, prevState) {
if(prevState.name !== this.state.name) {
document.title = `${this.state.name}'s counter`
}
}
incrementCount() {
this.setState(state => {
return { count: state.count + 1 }
});
}
decrementCount() {
this.setState(state => {
return { count: state.count - 1 }
})
}
updateName(e) {
this.setState({ name: e.target.value })
}
render() {
return (
<div>
<label>
Name:
<input
type='text'
name='name'
placeholder='Insert name'
defaultValue={this.state.name}
onChange={this.updateName}
/>
</label>
<br/>
<button onClick={this.decrementCount}>-1</button>
<span>{this.state.count}</span>
<button onClick={this.incrementCount}>+1</button>
</div>
);
}
}
Before we continue, lets dissect the component to understand how it works.
Firstly, we declare an initial state in the constructor for count
and name
. We also declare functions to update these states: decrementCount
, incrementCount
and updateName
. To be able to invoke these functions with DOM events, we must explicitly bind them with this
, as shown in the constructor.
Secondly, we introduce two lifecycle methods - componentDidMount
and componentDidUpdate
. The former is to set the initial document title when the component mounts. The latter is to update the document title on subsequent re-renders when name
changes.
Finally, the render
function returns JSX to declare markup including interactions between DOM elements and component state.
Functional component using hooks (after)
Now it's time to see an equivalent as a functional component using hooks.
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [name, setName] = useState('Joe')
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `${name}'s counter`
}, [name])
return (
<div>
<label>
Name:
<input
type='text'
name='name'
placeholder='Insert name'
defaultValue={name}
onChange={e => setName(e.target.value)}
/>
</label>
<br/>
<button onClick={() => setCount( count - 1 )}>-1</button>
<span>{count}</span>
<button onClick={() => setCount( count + 1 )}>+1</button>
</div>
)
}
As shown above, there is quite a lot of difference in the component after migrating to hooks. You may notice that the structure of the component has changed and it is relatively easier to interpret what is going on. There are also considerably less lines of code to achieve the same functionality as the class-based component.
As we did earlier, let's dissect this component too.
From React
, we import two hooks: useState
and useEffect
What are useState and useEffect?
useState
is used to declare state in your functional component. You must invoke it with an initial value and it will return an array of two values - current state and a function to update it.
useEffect
is used to manage side-effects in your functional component. It receives a function as a parameter which will handle an action to be invoked after the component renders. It is possible to declare multiple effects in a component. This helps to maintain separate concerns for each side-effect, rather than creating conditional logic in one function.
How are they used in this component?
In the new Counter
component, we apply the useState
hook to declare two state variables: name
and count
. Both have respective functions to update them: setName
and setCount
.
We call useEffect
to change the document title post-render. A dependency array is supplied as the second argument to ensure that the side-effect is only triggered when name
changes.
Similarly to the class-based component, we use JSX to declare markup and binds events to DOM elements. Notice how we can pass the functions provided by useState
directly to our event handlers. This avoids us having to manually define functions to set our state.
Summary
There we have it! We have successfully migrated a class-based component to a functional component using hooks. It is worth noting that most features of class-based components can now be achieved using functional components thanks to hooks. You can also create your own custom hooks, but we'll save that for another day.
Dont worry! This does not mean you must migrate all of your existing code to use hooks, but it is something to consider when you are building new ones.
Read more about hooks here!
Here are Codepen links for the snippets:
Counter as a class
Counter as a function with hooks
Top comments (1)
This is great Sub, thanks for sharing!
There's a good visual representation of this as an example slide deck I can across the other day, here: react-conf-2018-hooks-intro.netlif...