The React Class approach has long been the only way to create a state and lifecycle managed component. However, as practice has shown, the approach had a number of disadvantages. The React team added a new approach to the existing one in order to fix the shortcomings of the old one, and also added new capabilities for managing state.
In this blog I am going to share our experience of using React Hooks and compare the new approach with React Class. This article describes only the most significant changes, and will allow you to learn more about the approaches for both beginners and experienced developers.
What is React Hooks
Starting with version 16.8, React has a new approach to describing the lifecycle and state of a component. According to the documentation, React Hooks brings a number of improvements to efficiently writing components, and also allows you to solve some of the difficulties in the applied patterns. While React Hooks are a new approach to writing components, they are only recommended, not required. According to the React team, there are no plans to remove the classes, and both approaches can be used in the same project (however, a component should only use one of the approaches).
Source: unsplash.com
Less Repetitive Code
React provides a lot of possibilities for sharing code. A certain computation can be moved into a shared function and reused by other components. The component itself is shared code and can be easily reused in the application. However, if you need to reuse only the behavior or state of a component, then in the case of a React Class component, this is a difficult task in implementation.
Custom React Hooks allow you to combine coherent code that is directly responsible for the state or lifecycle of a component and make it shared between components.
// Shared Hook const useClicker = () => { // Set state const [counter, setCounter] = useState(0) // Define function const increment = useCallback(() => { setCounter(counter + 1) }, [counter]) // Add effect useEffect(() => { setCounter(1) }, []) return [counter, increment] } const Button = () => { // Using shared hook const [counter, increment] = useClicker() return <button onClick={increment}>Clicks: {counter}button> } const Submit = () => { // Using shared hook const [counter, increment] = useClicker() return <input type="submit" onClick={increment} value={`Clicks: ${counter}`} /> }
Less Definition Code
The ability to write a component without using a class existed in earlier versions, but such functional components did not have the ability to describe their lifecycle and state. At the moment when the component needed to have a state, the component needed to be wrapped in a class, declare `constructor` and transfer tags to the `render` method.
In addition to requiring a functional approach to a component (which reduces the number of lines of code to declare a component), React Hooks also allow for code to be reused, which can significantly reduce the number of lines of code.
const Button = () => { // Commented out code shows the similar code // written using React Class // constructor() { // super() // this.state = { counter: 0 } // } // setCounter(counter) { // this.setState({ counter }) // } const [counter, setCounter] = useState(0) // componentDidMount() { // this.setCounter(1) // } useEffect(() => setCounter(1), []) // render() { // const { counter } = this.state // return // } return <button onClick={() => setCounter(counter + 1)}>Clicks: {counter}button> }
Less Lifecycle Code
The React Class approach allows you to control a component at various lifecycle stages of a component using the `componentDid *` and `componentWill *` methods. However, as the application develops, the component may become more complex, and it will be necessary to extend the lifecycle methods to carry out additional logic. As a result, lifecycle methods become cumbersome, additionally, the code in these methods is difficult to understand as it is cut off from the rest of the code that can manage state.
`useEffect` allows you to control `componentDidMount`, `componentDidUpdate` and `componetWillUnmount` in a single method, and with the use of a custom hook, this logic can be combined with state management to improve the readability and understanding of the code.
// This example shows compounding useEffect methods in component // First hook contains useEffect to set the letter const useLetter = () => { const [letter, setLetter] = useState(null) useEffect(() => { setLetter('A') }, []) return letter } // Second hook contains useEffect to set the digit const useDigit = () => { const [digit, setDigit] = useState(null) useEffect(() => { setDigit('1') }, []) return digit } // useLetter & useDigit hooks applied const Button = () => { const letter = useLetter() const digit = useDigit() return <div>{letter} : {digit}div> }
Less HOC Wrappers
One of the patterns for shared code is High Order Components. There are a significant number of plugins, for example recompose, which allow you to quickly expand the capabilities of a component and customize its behavior. However, wrapping components results in multi-wrapped components as well as a deeply nested DOM tree of elements, which can have a performance impact on component rendering.
React Hooks allow you to take logic out of the High Order Component and reuse it in the component itself. With this approach, the logic will be injected into the component without wrapping the component and without creating deeply nested nodes.
// Shared Hook const useLetter = () => { const [letter, setLetter] = useState(null) useEffect(() => { setLetter('A') }, []) return letter } // Customized hook that uses shared hook const useLetterAndDigit = () => { const letter = useLetter() const [digit, setDigit] = useState(null) useEffect(() => { setDigit('1') }, []) return [letter, digit] } // Custom hook applied const Button = () => { const [letter, digit] = useLetterAndDigit() return <div>{letter} : {digit}div> }
Less Context Problems
The behavior of `this` in Javascript is already complicated. In React Class, for a React beginner, context can be an additional challenge in understanding how a component works. For example, a component’s React Class methods usually have `.bind (this)` to set the context. Such code, unfortunately, increases the codebase and can also cause an error if not set.
React Hooks do not require the use of this context, only certain variables are used both inside the hooks and in the template.
const Button = () => { // Commented out code shows the similar code // written using React Class // constructor() { // super() // this.state = { counter: 0 } // this.setCounter = this.setCounter.bind(this) // <===== sets context // } // setCounter() { // this.setState({ counter: this.state.counter + 1 }) // } const [counter, setCounter] = useState(0) // render() { // const { counter } = this.state // return // } return <button onClick={() => setCounter(counter + 1)}>Clicks: {counter}button> }
Wrap Up
Undoubtedly, React Hooks solved not only the above problems, but also brought a number of other benefits, for example, in optimizing minified code. However, React Hooks have a number of restrictions and rules that must be followed. Failure to follow them can also lead to a complication of the codebase, the creation of hard-to-catch bugs, or poor performance.
This article compared React Class and React Hooks approaches based on my own personal experience. For a deeper comparison, I recommended to refering to the official React Hooks documentation.
For more than 17 years, gap intelligence has served manufacturers and sellers by providing world-class services monitoring, reporting, and analyzing the 4Ps: prices, promotions, placements, and products. Email us at info@gapintelligence.com or call us at 619-574-1100 to learn more.