Blog -
Manage State Easier with React Hooks
In my daily work at FloQast, I’ll often run into a particular situation with React development. In order to toggle CSS effects on certain components, I will often:
- Convert the functional component to a class component and add local state
- Create a higher-order component if there’s common logic across multiple components
- Thread props through several components to their final consumers
- Make updates and write tests
That’s a lot.
I’ve often wondered if there’s a better way to do this — and now there is! Since the React team has released version 16.8, we can tackle these problems in a very elegant way using React Hooks.
What are React Hooks?
According to React’s official site, Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
Based on the definition, your first reaction might be, “Won’t I just be rewriting all my class components?!” React Hooks are far more than a refactor of the same React APIs.
Add Local State to Functional Component
Let’s check out a button component when a user clicks on it to change its background color:
import React, { useState } from ‘react’; const MyButton = () => { const [background, setBackground] = useState('blue'); const btnStyle = { background: background }; const changeBackground = () => { setBackground({ background: 'green' }); }; return <Button onclick={changeBackground} style={btnStyle}> My Button </Button>; }
The above component has a simple state variable background
to record what is the current button background color.
Is there a way to do this with a functional component? Yes, with the help of the React Hooks useState
api. Let’s see how we implement it in Hooks:
import React, { useState } from ‘react’; const MyButton = () => { const [background, setBackground] = useState('blue'); const btnStyle = { background: background }; const changeBackground = () => { setBackground({ background: 'green' }); }; return <Button onclick={changeBackground} style={btnStyle}> My Button </Button>; }
As you can see, the React Hooks API enables local state in functional components, reduces class-related boilerplate code, and in turn makes the functional component more powerful.
Extract Common State Logic
Choosing to use Hooks in stateless functional components also helps you get rid of boilerplate. Think how many times you’ve written out class constructor initialization and binding functions for your methods. Let’s see the difference using hooks:
import React from 'react'; class GetTime extends React.Component { constructor(props) { super(props); this.state = { time: null }; } checkTime() { this.setState({ time: new Date() }); } componentDidMount() { this.interval = setInterval(this.checkTime(), 1000); } componentWillUnMount() { clearInterval(this.interval); } render() { return <div>Current Time is: {this.state.time}</div> } }
Now let’s look at after by using Hooks
:
import React, { useState, useEffect } from ‘react’; const useTime = () => { const [time, setTime] = useState(null); useEffect(() => { const interval = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(interval); }, [time]); return time; } const GetTime = () => { const time = useTime(); return <div>Current Time is: {time}</div>; }
From the comparison we can see there is a useTime
function that wraps the get current time logic and sets the time to a local state variable. Now, any functional component can use it to display the current time.
The benefit of Hooks
here is we simplify the code by extracting the common state logic.
No More Wrapper Hell
Let’s take a look at another component:
import React from 'react'; import ReactDOM from 'react-dom'; const withCounter = Component => { return class ComponentWithCounter extends React.Component { state = { count: 0, }; handleDecrement = () => { this.setState({ count: this.state.count - 1 }); }; handleIncrement = () => { this.setState({ count: this.state.count + 1 }); }; render() { const { count } = this.state; return ( <Component {...this.props} count={count} onIncrease={this.handleIncrement} onDecrease={this.handleDecrement} /> ); } }; }; const App = ({ count, onIncrease, onDecrease }) => { return ( <div> <div>Current count: {count}</div> <div> <button onClick={onDecrease}>-</button> <button onClick={onIncrease}>+</button> </div> </div> ); }; const AppWithCounter = withCounter(App); ReactDOM.render(<AppWithCounter />, document.getElementById('root'));
Look familiar? Yes, I’ve seen this a lot too.
How about we turn the above component to something like below:
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; const useCounter = () => { const [count, setCount] = useState(0); const onIncrease = () => setCount(count + 1); const onDecrease = () => setCount(count - 1); return [ count, onIncrease, onDecrease ]; }; const App = () => { const [ count, onIncrease, onDecrease ] = useCounter(); return ( <div> <div>Current count: {count}</div> <div> <button onClick={onDecrease}>-</button> <button onClick={onIncrease}>+</button> </div> </div> ); }; ReactDOM.render(<App />, document.getElementById('root'));
No wrapper or class with a nice and clean functional component implementation.
Getting Started with React Hooks
Hooks
are beneficial to us in several ways. They:
- Enable local state and lifecycle in functional components
- Let you extract shared logic into custom hooks
- Significantly simplify code and help you get out of wrapper hell
Getting started isn’t painful!
See? Advocating for Hooks
doesn’t mean you have to refactor every class component in your codebase. Use them where it makes sense and before long you’ll find yourself…hooked.
If you are not convinced enough, hooks official docs has some decent arguments to consider, and I hope my experience is helpful.
Back to Blog