Remember Gauge Generator app? I initially wrote it using React classes and then optimized it's performance using React PureComponent.
Now let's see how it can be rewritten using the talk-of-the-react-town - Hooks.
What the hook?
Put it simply, Hooks lets you to use state and other react features in your function components. For example, if you need state, you can use useState hook. And, if you need context, you can use useContext.
Apart from the built-in hooks, you can create your own custom hooks and share the functionality across your React components (remember mixins, but a LOT better than that).
You can get an overview of React hooks or if you are really interested, do a deep dive in Dan's blog.
Let's get on to our business - convert a React class component that uses State to a function component using Hooks.
React class component with State
The component that I am going to use is the InputColor
of Gauge Generator app. This component wraps the SketchPicker
color picker tool and let the user to select a color from the pop-up. It has one state variable - pickerOpen
to indicate if the pop-up is opened or not.
For brevity, I omitted some code of this class.
import React, { PureComponent } from "react"
class InputColor extends PureComponent {
state = {
pickerOpen: false
}
togglePicker = () => {
this.setState(state => ({ pickerOpen: !state.pickerOpen }));
}
render() {
const { pickerOpen } = this.state;
return (
<div>
Picker State: {pickerOpen ? "Open" : "Closed"}
<button onClick={this.togglePicker}>Toggle</button>
</div>
);
}
}
Here we have a simple React component, written as a Class and holds a State variable. To convert this into a functional component with hooks, lets determine the state we need to track. In our case, we need to track a single pickerOpen
boolean value in state. We can use useState
hook for this purpose.
useState hook
useState
function takes in an initial value of the state and returns two values: [currentStateValue, callbackToSetStateValue]
So, in our case, we can convert the state and its modified method as below:
Refactoring state
/* Before refactor */
state = {
pickerOpen: false
}
togglePicker = () => {
this.setState(state => ({ pickerOpen: !state.pickerOpen }));
}
/* After refactor using useState hook */
const [pickerOpen, setPickerOpen] = useState(false);
const handlePickerButtonClick = e => {
setPickerOpen(!pickerOpen);
}
If you notice, we got rid of using this
in the refactor. Because, in functional component, you don't need to use this
and all the functions / variables are wrapped in closures, so you can directly access them.
With this change, now whenever you update the state value using setPickerOpen
callback, React will re-render your whole component again.
Now that we have our state built, it's time to use it in our render logic.
Refactoring render
const [pickerOpen, setPickerOpen] = useState(false);
const handlePickerButtonClick = e => {
setPickerOpen(!pickerOpen);
}
return (
<div>
Picker State: {pickerOpen ? "Open" : "Closed"}
<button onClick={handlePickerButtonClick}>Toggle</button>
</div>
);
Putting it all together
We have our state and render function, lets wrap it in a functional React component:
const InputColor = () => {
const [pickerOpen, setPickerOpen] = useState(false);
const handlePickerButtonClick = e => {
setPickerOpen(!pickerOpen);
}
return (
<div>
Picker State: {pickerOpen ? "Open" : "Closed"}
<button onClick={handlePickerButtonClick}>Toggle</button>
</div>
);
}
No more classes! yay!!
Hey, what about the performance?
React will re-render your function component when ever there's a change in the prop values. In Class component, we have PureComponent
or shouldComponentUpdate
to help us with avoiding re-renders. PureComponent
will do a shallow comparision and using shouldComponentUpdate
, you can write your own comparison logic.
But, How can we do the same in the function component? React.memo
.
Just wrap your function component with React.memo
and you will get the functionality of PureComponent
in here.
For example,
const InputColor = React.memo( ({value}) => {
/* ... other logic ...*/
return <p>{value}</p>
});
With this change, React will not re-render our InputColor
component, if the passed in value
did not change.
Conclusion
In this post, I touched upon the very basic of React hooks - useState
. In the coming articles, lets dive further and refactor our Gauge Generator app using some advanced hooks concept. Hang tight!