DEV Community

Luiz Américo
Luiz Américo

Posted on

From React to Web Components: using hooks

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, 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. 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:

  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;
  });
Enter fullscreen mode Exit fullscreen mode

To overcome this, just add an empty array as second parameter.

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.

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:

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;
});
Enter fullscreen mode Exit fullscreen mode

So we can implement the feature:

  const messagesRef = useRef();

  useEffect(() => {
    if (messagesRef.current) {
      messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
    }
  }, [messages]);
Enter fullscreen mode Exit fullscreen mode

The final code, with setup instructions, can be found here. BTW, there's no build step, a benefit of web components world!

For fun, i also created a LitElement version of same app 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.

Top comments (0)