I’m fairly new to React but after watching the React Conf 2018 I feel a bit conflicted about the traditional ways of setting state, context, and React’s built-in lifecycle methods. I was initially super excited to start learning React but after getting to the point of a solid foundation I found myself cringing a bit for several reasons. Functional programming with React means copy and pasting the same overly robust typed patterns over and over again even with little-to-no differentiation – it just feels dirty. Passing down props through multiple components was a nightmare before Redux’s store but even that requires quite a bit of code repetition. This functional, explicit, way of writing code makes for more reusable micro-components on the front-end which is awesome to logically split up a code base while making component testing easier. The problem is that state within a component is just a plain old javascript object.
Let’s take a look at the following simple component example:
Traditional Clock Timer CodePen Without React Hooks >
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
At first, this seems to make simple examples easy to comprehend, but when it comes to more complex problems it makes it difficult to follow the intended logical trail of state/store between numerous components – especially if you’re inheriting a code base. This implementation nudges the developer to split up components in attempt to make each component’s intention very singular and reusable, but state is still tied to each class component. The main issue is that the current implementation of state management doesn’t allow you to extract each individual item within a component’s state into reusable pieces of code.
constructor(props) { super(props); this.state = {date: new Date()}; }
So, we see that this component has an initial state set which is a date, which is pretty simple, but let’s imagine if we have a ecommerce limited time promotions app with numerous different components that also require a date state so that each view of a promotion can have it’s own countdown. The introduction of React Hooks proposes that we should stop using the js class syntactic sugar due to these unintentional bad patterns.
“We found that class components can encourage unintentional patterns that make these optimizations fall back to a slower path… Hooks let you use more of React’s features without classes.“
Next, let’s think about this.state and this.setState. This. This. This. This. This. But what is this?! We have to use this because Using React {Component} is a class. Since we’re looking to phase out classes with the introduction of hooks we can also get rid of all the this. typing and all the .bind(this) within our event handlers. First, take a look at the new way of setting state vs the old way. The whole constructor(props){ super(props); this.state = {date: new Date()}; } is 68 total characters to type out and that seems like far too much to me. With React’s new useState Hook this would turn into const [date, setDate] = useState(new Date()); which is now only 45 total characters – 34% less typing. Woohoo! Now, let’s look at how setState would look this.setState({ date: new Date() }); 35 characters isn’t all too bad but using this.setState doesn’t really tell you about what state you’re going to set until you reach the inside of the object itself. With our new useState hook we can now set the date state by using a clearly named function called setDate that was initialized within our initial state declaration. Setting the date state now looks like this setDate(new Date()); which is 20 characters – 43% less typing. However, it’s not the less typing that has me the most excited about hooks, it’s having functions that actually describe exactly what they’re intended to do.
The useState() and setDate() from our const [date, setDate] = useState(new Date()); declaration are crystal clear in their intention and it’s so elegant, but this still doesn’t make them reusable. Enter custom hooks, the React solution to extract component logic into reusable functions instead of passing it down through props or using higher order components like Redux connect(). Redux connect forces the developer to add more components to an already crowded component tree, while React Hooks allow the developer to more cleanly share logic. Let’s look at our new Clock component so far with the useState hook implemented:
Working Clock Timer CodePen With React Hooks >
import { useState, useEffect } from 'react'; function Clock(props) { const [date, setDate] = useState(new Date()); //Replaces componentDidMount and componentWillUnmount useEffect(() => { var timerID = setInterval( () => tick(), 1000 ); return function cleanup() { clearInterval(timerID); }; }); function tick() { setDate(new Date()); } return ( <div> <h1>Hello, world!</h1> <h2>It is {date.toLocaleTimeString()}.</h2> </div> ); }
You’ll notice we’ve also been able to remove the repetitive componentWillMount and componenetWillUnmount by using another built in React Hook called useEffect() – this drastically cleans up our code! No more ‘this’! Ugh, we STILL can’t reuse our date state though… What gives? That’s right, we need to create a custom hook by extract our date state into a function.
Working Clock Timer with CUSTOM React Hooks >
import { useState, useEffect } from 'react'; function Clock(props) { //Here we reference our custom hook const timer = useNewTimer(new Date()); return ( <div> <h1>Hello, world!</h1> <h2>It is {timer.toLocaleTimeString()}.</h2> </div> ); } /////////////////////////////////////////////// //Below we've created a custom reusable hook ////////////////////////////////////////////// function useNewTimer(currentDate) { const [date, setDate] = React.useState(currentDate); React.useEffect(() => { var timerID = setInterval( () => tick(), 1000 ); return function cleanup() { clearInterval(timerID); }; }); function tick() { setDate(new Date()); } return date; }
Conclusion
I really hope hooks catch on in the wider React community. With a minor learning curve I think we can all look forward to more readable code that makes it easier to reuse logic. Most importantly though I believe hooks will have the biggest impact on more efficiently translating business logic into code. If you can’t tell, I’m not a big fan of JS class syntax or all the ‘this’ and ‘bind’ that comes with it and given this most recent React addition I think we’re seeing the industry give the ES6 class syntax a bit of a cold shoulder for numerous reasons.