From React to Web Components: using hooks
Tags: react, webcomponents, hooks
Published: 2019-10-09 00:21:15

The recent rise of React Hooks, followed by a lot of people claiming all sort of benefits, raised my interest in it. There was just one problem: i don't use, nor i'm not interested in, React.

Hopefully there's [haunted](https://github.com/matthewp/haunted), a library that enables hooks for web components by providing an API similar to the React one.

## What and why

I picked some non trivial React Hooks demos and converted to web components / haunted so i could:

 * evaluate how well it integrates with web components ecosystem
 * check how feasible is to reuse existing code
 * check if it matches my coding requirements / taste

## A chat app with Firebase

The original project can be found at [freeCodeCamp](https://www.freecodecamp.org/news/how-to-build-a-real-time-chatroom-with-firebase-and-react-hooks-eb892fa72f1e/). It's a simple chat app that uses Firebase as backend.

Converting, and getting to work, was basically straight forward. In fact, it took a little more time because i did some changes like changing from Real Time Database to Firestore and using context to provides db instance. 

The only issue related to hooks was the messages being pulled from the backend in a loop even without new data. The reason was a lack of understanding of how `useEffect` works. Below is the original code:

```js
  useEffect(() => {
    const handleNewMessages = snap => {
      // avoid updating messages when there are no changes
      const changes = snap.docChanges()
      if (changes.length) {
        setMessages(snap.docs.map(doc => doc.data()))
      }      
    }
    const unsubscribe = chatRoomMessages.onSnapshot(handleNewMessages);
    return unsubscribe;
  });
```

To overcome this, just [add an empty array as second parameter](https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect).

## Adding a new feature

In the original app, when a new message is added to a full room it gets hidden. By scrolling the element that holds the messages is possible to fix that.

Such feature can be implemented with React Hooks using [`useEffect` and `useRef`](https://stackoverflow.com/questions/56086130/reactjs-how-to-scroll-to-an-element-with-hooks-functions). 

The only problem is that, in Haunted / lit-html, there's not a out of the box way to get an element reference. It needs a [directive](https://github.com/matthewp/haunted/issues/65#issuecomment-503645563):

```js
const ref = directive((refInstance) => (part) => {
  if (!(part instanceof AttributePart)) {
    throw new Error('ref directive can only be used as an attribute');
  }
  refInstance.current = part.committer.element;
});
```

So we can implement the feature:

```js
  const messagesRef = useRef();

  useEffect(() => {
    if (messagesRef.current) {
      messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
    }
  }, [messages]);
```

The final code, with setup instructions, can be found [here](https://github.com/blikblum/wc-hooks-demos/tree/master/realtime-chat). BTW, there's no build step, a benefit of web components world!

For fun, i also created a [LitElement](https://lit-element.polymer-project.org) [version of same app](https://github.com/blikblum/wc-hooks-demos/tree/master/realtime-chat/lit-element) to see how implementations compare.


## Final words

The process of converting from React to Haunted / lit-html was pretty much challengeless. Just minor issues like the need to map React `onChange` to native `input` event.

Regarding the Hooks feature itself, the only obstacle i faced was the lack of 
an out of box way to declaratively access a element instance, like React `ref` feature.

And what about the supposed benefits of using hooks? Definitively, handling setup / teardown routines together (using `useEffect`) is better than adding such routines to `connectedCallback` / `disconnectedCallback`. I also like the declarative way to get an element instance instead of using CSS selectors.

On the other side, the class based approach allows to a more natural way to declare reactive properties / attributes and have the, yet not unleashed, potential of decorators.

Time allowing, i will convert one or two more apps based on React Hooks.