Comparing class components to hook-based components
We will look at the same component written with hooks and classes. Both use this child component:
const CounterBox = (props) => {
return <div className="output">{props.counterValue}</div>;
};
Class component
import CounterBox from "./CounterBox";
class CounterClass extends React.Component {
constructor(props) {
super(props);
this.state = {
counterValue: 0,
};
}
addOne() {
this.setState({
counterValue: this.state.counterValue + 1,
});
}
render() {
return (
<React.Fragment>
<button onClick={() => this.addOne()}>Add One</button>
<CounterBox counterValue={this.state.counterValue} />
</React.Fragment>
);
}
}
Hook version
import React, { useState } from "react";
import CounterBox from "./CounterBox";
function CounterHook() {
const [counter, setCounter] = useState(0);
const handleClick = () => setCounter(counter + 1);
return (
<React.Fragment>
<button onClick={handleClick}>Add One</button>
<CounterBox counterValue={counter}></CounterBox>
</React.Fragment>
);
}
Main differences
Obviously with hooks we use a function, not a class. We can therefore think of them as simple components that are able to handle state as well as props.
We have written the hook as a declared function but we could just as easily write it as a function expression or arrow function.
With the class, the state versioning is managed via the following properties on the
React.Component
class:this.state
(start state) andthis.setState
(the change).In both cases the function that actually creates the change is handled via an arrow function. With the hook we use the customary name
handleClick
.The same process is managed in the hook via the
useState()
method. We effectively set ‘before’ and ‘after’ destructured variables on this method:const [counter, setCounter] = useState(0); // Schematically: const [val, setVal] = useState(initialVal);
Binding with class component state
In the class component example we use an inline arrow function to execute addOne()
however the official way to do it is to bind the event via the constructor method to avoid the problems created by this
scope in classes. Using arrows is mostly fine and is less verbose, but it is important to understand the ‘official’ approach.
The way to write the state change in this way is:
class CounterClass extends React.Component{
constructor(props){
super(props);
this.state = { counterValue: 0};
this.handleClick = this.handleClick.bind(this); // bind the click to the class scope
}
handleClick(event){
this.setState({
counterValue: this.state.counterValue + 1;
});
}
render(){
return (
<button type="button"
onClick={this.handleClick}> // reference the class scope with `this`
Click Me
</button>
<CounterBox counterValue={this.state.counterValue}></CounterBox>
);
}
}
How hooks improve on class components
State, when managed via hooks can be easily decoupled from a specific component and reused elsewhere. This also means it can be tested separately from any specific component where it is applied. This is much harder to do with classes which are closely entangled with the non-state aspects of a component through
this
and binding:With Hooks, you can extract stateful logic from a component so it can be tested independently and reused. Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.
Hooks simplify lifestyle methods and the use of
componentDidMount
etc through theuseEffect
hookClasses and
this
are confusing generally. Functions are easier to grasp and read.
Relation to function components
You can think of hooks as function components (previously known as stateless components) plus state. Previously function components were ‘dumb’ components. That is, they didn’t allow for state management and generally used for layout. They could receive data as props but not state. This changes with hooks: you can add state to functions and no longer need to use classes exclusively for state. In fact you no longer need to use classes at all, unless you want to.
The stateful portion of a function component must be placed before the
return()
statement in the function.