Reducing component complexity with React Hooks
Tags: react, javascript
Published: 2020-11-10 14:08:42

This is my first post here, so go easy on me 😅

---

Essentially, a hook is just a javascript function prefixed with `use` which is allowed to invoke other hooks.<sup>1</sup> Within reason, you can make a custom hook do whatever you want. Network requests, state management, redirecting the user, invoking other hooks, etc. You can get really creative with them.

Over the past few months, one of my favorite ways to use hooks has been to abstract out complicated component logic.

### What do I mean by this?

Imagine you have a Newsfeed component. It loads and renders posts from your api. The simplest implementation is to inline everything at the top of your component:

```react
// Newsfeed.jsx
import React, { useState } from 'react'

const Newsfeed = () => {
  const [loading, setLoading] = useState(false)
  const [posts, setPosts] = useState([])

  const getPosts = () => {
    setLoading(true)
    fetch('/v1/posts')
      .then(data => data.json())
      .then(data => setPosts(data))
      .then(() => setLoading(false))
  }

  return (
    <div>
      {posts.map((post, index) => (
        <div key={index}>
          <h1>{post.title}</h1>
        </div>
      ))}
      {loading ? (
        <p>Loading...</p>
      ) : (
        <button onClick={getPosts}>Load Posts</button>
      )}
    </div>
  )
}

export default Newsfeed
```

### The problem 
The above implementation works, but I want you to imagine for a moment that suddenly instead of two states to manage, we have 5. We've got `loading`, `posts`, `page`, perhaps we've got `open` to track whether or not the user clicked into a post, and even `replying` to track if they're replying to that post. Instead of just `getPosts`, we have a bunch of different functions. Say, `getPost`, `getPostComments`, `postComment`, etc.

That's *a lot* of complexity to add to the top of a functional component. It's an unmaintainable amount of complexity. That isn't even considering how the render portion of that component will grow in complexity, too. Sometimes you can break one complex component into many smaller components, but you can't always cleanly separate the logic. 

### A solution
**Just don't include the functional complexity in the component.**

Take all of it, and isolate it into a hook.

Why a hook? Because the integration is easy and seamless. No need to set up reducers or contexts. You get lots of bonuses like being able to call other hooks or automatic rerenders when your state updates. 

The most important concept is that our hook, when used like this, must *return* everything required for the component to render. You can almost think of the return value of the hook as props being passed to the component.<sup>2</sup>

Let's see what it looks like after we import the hook, destructure the values returned, and <kbd>ctrl</kbd>+<kbd>x</kbd> and <kbd>ctrl</kbd>+<kbd>v</kbd> all of the logic away.

```react
// Newsfeed.jsx 
import React from 'react'
import useNewsfeed from './useNewsfeed'

const Newsfeed = () => {
  // Destructure the value that the hook returns 
  const {
    state: { loading, posts },
    getPosts
  } = useNewsfeed()

  return (
    <div>
      {posts.map((post, index) => (
        <div key={index}>
          <h1>{post.title}</h1>
        </div>
      ))}
      {loading ? (
        <p>Loading...</p>
      ) : (
        <button onClick={getPosts}>Load Posts</button>
      )}
    </div>
  )
}

export default Newsfeed
```
```js
// useNewsfeed.js
import { useState } from 'react'

export default () => {
  // Our hook manages our state for us 
  const [loading, setLoading] = useState(false)
  const [posts, setPosts] = useState([])

  // It also manages our functionality
  const getPosts = () => {
    setLoading(true)
    fetch('/v1/posts')
      .then(data => data.json())
      .then(data => setPosts(data))
      .then(() => setLoading(false))
  }

  // Finally, it exposes only what is required by the component
  return {
    state: { loading, posts },
    getPosts,
  }
}
```

### Should you do this? 

The answer is... it depends. It's like asking if you should inline a styling rule or if you should put it into a stylesheet. There are valid situations for both.

There are a couple of benefits to consider:
1. It cleanly separates your concerns without adding much complexity.<sup>3</sup>

2. It cleans up your imports *a lot*. You don't have 20 imports from a component library inbetween your network request imports. 

3. Legibility. You can take one glance at the component (or the hook!) and you understand what's going on.

4. It tends to consolidate logic into one location, which makes locating, understanding, and altering it easier.

5. Seamless integration. No need to refactor anything.

6. You can completely forgoe the implementation. Our hook could look like this and our component would be none the wiser: 
```js
// useNewsfeed.js

// This always returns a fixed value, meaning 
// the component always behaves as if the posts loaded already.
export default () => {
  return {
    state: { 
      loading: false,
      posts: [{
        id: 6,
        title: 'Hooks are cool'
      }]
    },
    getPosts: () => null,
  }
}
```

### Some very important caveats 

This doesn't replace the need for separating components into multiple smaller components. 

If you choose to do this, you *really* should be comfortable with how hooks work. 

You should also familiarize yourself with how hook dependencies work. For example, if you don't properly use things like `useCallback` and `useMemo`, you might end up with infinite loops and not understand why. 😅

If you haven't, I recommend you download an extension that warns you when you're using them incorrectly to spot things like missing hook dependencies. 

### 🧠 What do you think?

Do you already use hooks like this? 
Do you hate the idea of this?
Do you take it further and create tons of custom hooks?

Lemme know below 👋

---
<small>
<sup>1</sup> https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook
<sup>2</sup> Similarly, but not exactly the same. Remember, unlike props, React is choosing when to rerender everything based on certain things happening under the hood - like the value of `useState` or a dependency passed to `useCallback` changing.
<sup>3</sup> With a simple example like our Newsfeed app that only has two states and one function, it probably isn't worth the complexity this adds. It's up to you to decide what's right. Always ask yourself: "Can I quickly develop and maintain a mental model of this?"
</small>