DEV Community

Cover image for Why React hooks (Part II: Reusable code)
TrinhDinhHuy
TrinhDinhHuy

Posted on • Updated on

Why React hooks (Part II: Reusable code)

Prerequisite: Basic knowledge about React

This article is intended to give you an understanding of how React Hooks helps us to share common logic between components and what differences between using Hooks and other methods.

Take a look if you have not read the Part I

You spent 2 hours writing a beautiful functionality in your component and then just a minute later, your Boss wants the same thing ... but in another component. What should you do now?

πŸ˜… Easy, I will do some copy + paste stuff

Copy-paste

As a React developer, you will at some point run into the situation where you have to share some common logic between components. Higher-order components (HOCs) are well known in the React community for solving that kind of issue. However, I found HOCs a little bit complicated for beginners to get the idea while Hooks makes it way much easier and cleaner.

πŸ’ͺ Let's get started!

‍Our task today is to help Carey to teach her naughty twins, Zack and Cody , she will shout at them if they do something bad many times

β€’ Zack.jsx

class Zack extends React.Component {

    state = {
        numberOfBadActions: 0
    }

    componentDidUpdate(prevProps, prevState) {
       if (
        prevState.numberOfBadActions !== this.state.numberOfBadActions && 
        this.state.numberOfBadActions === 3
       ) {
        console.log('Use your head!')
        this.setState({numberOfBadActions: 0})
       }
    }

    doBadAction = () => {
        this.setState(state => ({numberOfBadActions: state.numberOfBadActions + 1}))
    }

    render() {
        return (
            <div>
                <p>Number of bad actions: {this.state.numberOfBadActions}</p>
                <button onClick={this.doBadAction}>Do bad action</button>
            </div>
        )
    }
}

export default Zack
Enter fullscreen mode Exit fullscreen mode

Look at Cody, I have to shout at him, too! 😫

0. Copy + paste

I've just copied Zack.jsx and rename the file to Cody.jsx and also change the component name to Cody

β€’ Cody.jsx

class Cody extends React.Component {

    state = {
        numberOfBadActions: 0
    }

    componentDidUpdate(prevProps, prevState) {
       if (
        prevState.numberOfBadActions !== this.state.numberOfBadActions && 
        this.state.numberOfBadActions === 3
       ) {
        console.log('Use your head!')
        this.setState({numberOfBadActions: 0})
       }
    }

    doBadAction = () => {
        this.setState(state => ({numberOfBadActions: state.numberOfBadActions + 1}))
    }

    render() {
        return (
            <div>
                <p>Number of bad actions: {this.state.numberOfBadActions}</p>
                <button onClick={this.doBadAction}>Do bad action</button>
            </div>
        )
    }
}

export default Cody
Enter fullscreen mode Exit fullscreen mode

I feel like patting myself on the back for solving this in just 1 second 😎

It looks good for now until later Carey decides to change the method of teaching her sons. In that case, we end up updating 2 components at the same time and could you imagine what if she has to do the same thing with her sons' friends, too. That's tricky because we have to copy and paste the code everywhere and the hard part is that we have to update all these components if something related to that logic needs to be changed πŸ˜–

1. Higher-Order Component

A higher-order component is a function that takes a component and returns a new component

In our case, all you need to do is to take all the teaching-related things outside the React component and move it into a HOC

export const withMom = (WrappedComponent) => {
    return class WithMom extends React.Component {
         state = {
             numberOfBadActions: 0
         }

         componentDidUpdate(prevProps, prevState) {
            if (
              prevState.numberOfBadActions !== this.state.numberOfBadActions && 
              this.state.numberOfBadActions === 3
            ) {
              console.log('Use your head!')
              this.setState({numberOfBadActions: 0})
            }
         }

         doBadAction = () => {
            this.setState(state => ({numberOfBadActions: state.numberOfBadActions + 1}))
         }

         render() {
            return (
                <WrappedComponent 
                    numberOfBadActions={this.state.numberOfBadActions} 
                    doBadAction={this.doBadAction}
                    {...this.props}/>
                )   
         }   
    }
}
Enter fullscreen mode Exit fullscreen mode

withMom HOC is a function that accepts one argument as Component and returns a new and enhanced Component with all logics related to teaching. Now you can use withMom HOC to wrap components like below πŸ‘‡:

β€’ Zack.jsx

class Zack extends React.Component {
    render() {
        return (
            <div>
                <p>Number of bad actions: {this.props.numberOfBadActions}</p>
                <button onClick={this.props.doBadAction}>Do bad action</button>
            </div>
        )
    }
}

export default withMom(Zack)
Enter fullscreen mode Exit fullscreen mode

β€’ Cody.jsx

class Cody extends React.Component {
    render() {
        return (
            <div>
                <p>Number of bad actions: {this.props.numberOfBadActions}</p>
                <button onClick={this.props.doBadAction}>Do bad action</button>
            </div>
        )
    }
}

export default withMom(Cody)
Enter fullscreen mode Exit fullscreen mode

HOC helps you to organize your code in a much better way. In our case, Zack and Cody components do not care about the teaching logic anymore because now the withMom HOC encapsulates that logic and passes it down to the wrapped component. And the amazing part is that if Carey wants to change her method, all we need to do is to tweak the code in only 1 place - withMom HOC.

πŸ˜“ Unfortunately, there is a major downside to using HOCs. Imagine that you have more than one HOC to consume then you start to face up with the problem of controlling all the passing down props and Wrapper Hell issue

export default withGrandPa( 
    withDad(
        withMom(
            Cody
        )
    )
)
Enter fullscreen mode Exit fullscreen mode

Then our DOM looks like this

<WithGrandPa>
    <WithDad>
        <WithMom>
            <Cody/>
        </WithMom>
    </WithDad>
</WithGrandPa>
Enter fullscreen mode Exit fullscreen mode

Normal people: Wrapper Hell

Me as an intellectual: My Asian family clean architecture πŸ‘ͺ

2. React Hooks

πŸ”₯ It would be a long story here, stay with me and I will walk you through step by step.

If you have not read the Part I of this series, I recommend you read it first then come back otherwise you may get lost

β€’ Step 1: Convert the Zack component to a function component

const Zack = () =>  {

    const [numberOfBadActions, setNumberOfBadActions] = React.useState(0)

    React.useEffect(() => {
        if (numberOfBadActions === 3) {
            console.log('Use your head!')
            setNumberOfBadActions(0)
        }
    }, [numberOfBadActions])

    const doBadAction = () => {
        setNumberOfBadActions(numberOfBadActions => numberOfBadActions + 1)
    }

    return (
      <div>
        <p>Number of bad actions: {numberOfBadActions}</p>
        <button onClick={doBadAction}>Do bad action</button>
      </div>               
    )
}

export default Zack
Enter fullscreen mode Exit fullscreen mode

β€’ Step 2: Write custom Hook

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.


const useYourHead = (initialNumberOfBadActions) => {
    const [numberOfBadActions, setNumberOfBadActions] = React.useState(initialNumberOfBadActions)

    React.useEffect(() => {
        if (numberOfBadActions === 3) {
            console.log('Use your head!')
            setNumberOfBadActions(0)
        }
    }, [numberOfBadActions])

    const doBadAction = () => {
        setNumberOfBadActions(numberOfBadActions => numberOfBadActions + 1)
    }

    return [numberOfBadActions, doBadAction]
}
Enter fullscreen mode Exit fullscreen mode

When we need to share logic between 2 functions, we extract the logic to a third function. The same thing is applied when we want to share logic between React components because they are functions and Hook is also a function.

I've just extracted all the code that we want to share to a custom Hook named useYourHead. We need to think about what arguments this function should accept and what it should return as custom Hook is just a normal function. In our case, useYourHead accepts the initial number of bad actions and return numberOfBadActions as well as doBadAction

β€’ Step 3: Use our custom Hook

const Zack = () =>  {

    const [numberOfBadActions, doBadAction] = useYourHead(0)

    return (
        <div>
            <p>Number of bad actions: {numberOfBadActions}</p>
            <button onClick={doBadAction}>Do bad action</button>
        </div>                 
    )
}

export default Zack
Enter fullscreen mode Exit fullscreen mode
const Cody = () =>  {

    const [numberOfBadActions, doBadAction] = useYourHead(0)

    return (
        <div>
            <p>Number of bad actions: {numberOfBadActions}</p>
            <button onClick={doBadAction}>Do bad action</button>
        </div>                 
    )
}

export default Cody
Enter fullscreen mode Exit fullscreen mode

3. Conclusion:

πŸš€ Hooks help us to inject reusable logic to React components without creating HOCs. As you can see, we don't have to deal with the issue of Wrapper Hell or the problem of passing down props through many component layers. πŸ‘πŸ‘πŸ‘

Here are some good resources for you:

πŸ™ πŸ’ͺ Thanks for reading!

Please leave your comments below to let me know what do you think about this article

✍️ Written by

Huy Trinh πŸ”₯ 🎩 β™₯️ ♠️ ♦️ ♣️ πŸ€“

Software developer | Magic lover

Say Hello πŸ‘‹ on

βœ… Github

βœ… LinkedIn

βœ… Medium

Oldest comments (0)