My First Custom Hook in React
Tags: react, tutorial, javascript
Published: 2019-11-14 15:35:45

If you want to follow along, here's the codesandbox with hooks: 
{% codesandbox nft5x %}

---

I've been slow the React Hooks game. First it was because my last company was on an older version of React and lately it's mostly been I just haven't focused on learning them and adding them to my code. 

It seems obvious to me that hooks are here to stay, so I've recently been [doing](https://reactjs.org/docs/hooks-overview.html) [some](https://overreacted.io/making-setinterval-declarative-with-react-hooks/) [reading](https://wattenberger.com/blog/react-hooks) and felt ready to jump into my codebase to practice. 

I read a bit about how hooks were potentially good replacements for higher order components (HOC). I recently created an HOC that was checking for window resizing and communicating whether the window size met our "mobile" screen width of 640 pixels or less. 

That component looked like this to start: 
```react
// connectResizer.js

import React, { Component } from 'react'

export default function withResizer(WrappedComponent) {
  return class ResizeHandler extends Component {
    constructor(props) {
      super(props)
      this.state = {
        isMobile: window.innerWidth < 640,
      }
    }

    componentDidMount() {
      window.addEventListener('resize', this.resizeWindow)
      this.resizeWindow()
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.resizeWindow)
    }

    resizeWindow = () => {
      this.setState({ isMobile: window.innerWidth < 640 })
    }

    render() {
      return <WrappedComponent isMobile={this.state.isMobile} {...this.props} />
    }
  }
}
```

Honestly, it works just as we needed. It passed an `isMobile` boolean prop to its wrapped component and we could go on our merry way implementing conditional logic like this: 

```react
// components/Navbar.js

function Navbar({ isMobile, org, user, baseUrl }) {
  if (isMobile) {
    return (
      <>
        <Dropdown>
          <AccountLinks isMobile={isMobile} baseUrl={baseUrl} />
        </Dropdown>
        <CartLink
          user={user}
          org={org}
          isMobile={isMobile}
        />
      </>
    )
  }

  return (
    <>
      <AccountLinks isMobile={isMobile} />
      <CartLink
        user={user}
        org={org}
        isMobile={isMobile}
      />
    </>
  )
}

export default withResizer(Navbar) // wrap that component to get access to isMobile in Navbar
```

But it's also a really great example of something that can be replaced with a `useEffect` hook: 
* it is using multiple React LifeCycle methods
* it has some internal state that needs to be communicated to and reused by other components
* it's pretty straightforward and easy to test

_Just a note that the following example is in TypeScript because we are currently migrating our codebase over to TypeScript and if I were to change this component, I would be rewriting it in TypeScript._

So, here's what the final hook function looks like: 
```react
// useResizer.ts

import * as React from 'react'
export default function useResizer(): boolean {
  const [isMobile, setIsMobile] = React.useState(window.innerWidth < 640);

  function handleSizeChange(): void {
    return setIsMobile(window.innerWidth < 640);
  }

  React.useEffect(() => {
    window.addEventListener("resize", handleSizeChange);

    return () => {
      window.removeEventListener("resize", handleSizeChange);
    };
  }, [isMobile]);

  return isMobile;
}
```

It is definitely less lines of code than our HOC. But is it more readable? Because hooks are still new to me, I'm not sure. But let's dive in to see what's going on. 

```react
  // useResizer.ts

  const [isMobile, setIsMobile] = React.useState(window.innerWidth < 640);
```
This one line using the `useState` hook gives us:
* our state value of `isMobile`,
* a setter `setIsMobile` that will take a value and update state to that given value,
* and a default value `window.innerWidth < 640`. 

We'll be calling that method to actually update our state when our hook is notified of changes to the window width. 

```react
  // useResizer.ts

  function handleSizeChange() {
    return setIsMobile(window.innerWidth < 640);
  }
```

Next is our callback we pass to our window event listeners. You can see this is using our `useState` helper to set the `isMobile` boolean value when `handleSizeChange` is called. 

Now the fun part 🙌

```react
  // useResizer.ts

  React.useEffect(() => {
    // add event listener - update our local isMobile state
    window.addEventListener("resize", handleSizeChange);

    // handle cleanup - remove event listener when effect is done
    return () => {
      window.removeEventListener("resize", handleSizeChange);
    };
  }, [isMobile]); // add dependency - only use our effect when this value changes
```

Finally, don't forget this uber important last line that is outside of our `useEffect` function: 
```react
// useResizer.ts

return isMobile;
```
This is the bit that is returning the actual value of `isMobile` and making it accessible to the components consuming `useResizer()`. 

At the end of the day, we would update the example above to look like this:
```react
// components/Navbar.js

function Navbar({ org, user, baseUrl }) { // notice isMobile is gone from props
  const isMobile = useResizer() // because now we use our hook!
  if (isMobile) {
    return (
      <>
        <Dropdown>
          <AccountLinks isMobile={isMobile} baseUrl={baseUrl} />
        </Dropdown>
        <CartLink
          user={user}
          org={org}
          isMobile={isMobile}
        />
      </>
    )
  }

  return (
    <>
      <AccountLinks isMobile={isMobile} />
      <CartLink
        user={user}
        org={org}
        isMobile={isMobile}
      />
    </>
  )
}

export default Navbar // no more HOC wrapper needed here, either!
```

Well, that's it. What do you think? I still have a lot to learn (including the gotchas) but it's starting to make sense to me.

Are you and your teams all-in on hooks or holding tight to class components?