# React [HOC](https://reactjs.org/docs/higher-order-components.html "Higher-Order Component")s - Hooks Version

React HOCs explained in the functional components way.

## Let's Go

* React HOCs are a pattern to abstract and share some logic accross many components.
* HOCs use containers as part of their implementation.
* You can think of HOCs as parameterized container component definitions.
* A React HOC is a pure function that... :
    * Takes a component.
    * Defines some functionality.
    * Returns one of the two:
        * A new component based on the given one, but with the additional functionality, **or**:
        * Another HOC.

### Scenario-Based Explanation

Say you have many different components that use the same pattern to subscribe to an external data source to render the fetched data, e.g.:

```js
const SubscribingUsersList = () => {

  const [users, setUsers] = useState([])

  const onChange = useCallback(() => DS.getUsers().then(data => setUsers(data)))

  useEffect(() => {
    onChange()
    DS.addChangeListener(onChange)
    return () => DS.removeChangeListener(onChange)
  }, [])

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
```

```js
const SubscribingBlogPost = ({ postId }) => {

  const [post, setPost] = useState(null)

  const onChange = useCallback(() => DS.getPost(postId).then(data => setPost(data)))

  useEffect(/* Same effect... */)

  return (
    <div>
      <h6>{post?.title}</h6>
      <pre>{post?.body}</pre>
    </div>
  )
}
```

...

So, let's use a HOC to abstract this fetch and subscription logic and share it across many components, like simpler versions of the above.

Our HOC will... :

* Accept as one of its arguments a child component.
* Create a new component that... :
    * Wrapps the given component.
    * Subscribes to `DS`.
    * Passes subscribed data as a prop to the given component.

```js
const withSubscription = (Component, fetcher) => props => {

  const [data, setData] = useState(null)

  const onChange = useCallback(() => fetcher(DS, props).then(json => setData(json))

  useEffect(/* Same effect... */)

  return <Component data={data} {...props} />
}
```

Now let's make new simple versions of our components, that **don't** manage any subscriptions, and only provide UI.
We'll expose the new subscribing version of them using `withSubscription`:

**Inside `UsersList.js`:**
```js
const UsersList = ({ data }) => {
  return (
    <ul className='UsersList'>
      {data?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

const usersFetcher = DS => DS.getUsers()

export default withSubscription(UsersList, usersFetcher)
```

**Inside `BlogPost.js`:**
```js
const BlogPost = ({ data }) => {
  return (
    <div className='BlogPost'>
      <h6>{data?.title}</h6>
      <pre>{data?.body}</pre>
    </div>
  )
}

const postFetcher = (DS, { postId }) => DS.getPost(postId)

export default withSubscription(BlogPost, postFetcher)
```

Nice!

### Improve it

To control the name of the passed prop, we could do as follows:

**In `withSubscription`:**
```js
const withSubscription = (Component, fetcher, passedPropName = 'data') => props => {
	...
	return <Component {...{ [passedPropName]: data }} {...props} />
}
```

(Note the new `passedPropName` argument and the new way we pass `data` using `passedPropName`)

**In `UsersList.js`:**
```js
const UsersList = ({ users }) => {
  return (
    <ul>
      {users?.map(user => (
        ...
      ))}
    </ul>
  )
}

...

export default withSubscription(UsersList, usersFetcher, 'users')
```

(Note the `users` prop in place of `data` and the new `'users'` parameter)

**In `BlogPost.js`:**
```js
const BlogPost = ({ post }) => {
	...
}

export default withSubscription(BlogPost, postFetcher, 'post')
```

(Same story - `post` instead of `data` and an additional `'post'` parameter)

## Important Notes

* [**Don’t Mutate the Original Component. Use Composition**](https://reactjs.org/docs/higher-order-components.html#dont-mutate-the-original-component-use-composition).
* [**Convention: Pass Unrelated Props Through to the Wrapped Component**](https://reactjs.org/docs/higher-order-components.html#convention-pass-unrelated-props-through-to-the-wrapped-component).
* [**Convention: Maximizing Composability**](https://reactjs.org/docs/higher-order-components.html#convention-maximizing-composability).
* [**Convention: Wrap the Display Name for Easy Debugging**](https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging).
* [**Static Methods Must Be Copied Over**](https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over).
* [**Refs Aren’t Passed Through**](https://reactjs.org/docs/higher-order-components.html#refs-arent-passed-through).
That’s because `ref` is not really a prop — like `key`, it’s handled specially by React. If you add a `ref` to an element whose component is the result of a HOC, the `ref` refers to an instance of the **outermost** container component, not the wrapped component.
The solution for this problem is to use the
[`React.forwardRef` API](https://reactjs.org/docs/forwarding-refs.html).

### Updating `withSubscription` to Wrap the Display Name

```js
const getDisplayName = component => component.displayName || component.name || 'Component'

const withSubscription = (Component, fetcher, passedPropName = 'data') => {
	const WithSubscription = props => {
    ...
	}

	WithSubscription.displayName = `WithSubscription(${getDisplayName(Component)})`

	return WithSubscription
}
```

### Copying Static Methods

```js
...
import hoistNonReactStatic from 'hoist-non-react-statics'

const getDisplayName = ...

const withSubscription = (Component, fetcher, passedPropName = 'data') => {
	const WithSubscription = props => {
    ...
	}

	hoistNonReactStatic(WithSubscription, Component)

	...
}
```