The React team introduced Hooks at React Conf 2018. Technically it might not have been a particularly big innovation, but it did change a lot in the approach for building UI components. It triggered a big wave of changes in many other open source projects and quite some new open source projects were created, like Redux Hooks, Apollo Hooks, TNG, etc. It even inspired Evan You (the creator of VueJS) to think about introducing Hooks in VueJS.
It’s no secret that ING works a lot with Web Components that are made using lit-html and LitElement — we even have an article about it. Initially, I was keen to build some kind of a support for Hooks in Web Components but in the Front End world if you do not bring your idea to life today, tomorrow you are too late. There is already a brilliant library which makes it possible to use Hooks together with Web Components. It’s name is Haunted.
I recently introduced this library in my team. Not everybody knew what Hooks are and what kind of benefits we can get from them. They were skeptical: it’s an extra dependency, there are no benefits for the user of the application, there are no performance improvements. Why would a developer use Hooks then?
I have managed to convince my team members, let’s see if I can convince you with this article.
We started using Haunted in two of our projects that initially were written using LitElement and our code became much cleaner. The code refactoring went smoothly as our applications are built with web components. It is not needed to begin from scratch, you can just rewrite it component by component. Moreover client side data in those projects is managed by Redux, so a huge part of application remained untouched. But the component classes and the logic around them became more compact and more readable. And at the end everyone in the team are happy with the result.
I will try to explain to you now in more details why the Hooks are so cool. But first, let’s introduce some key concepts.
In this example,
html is just a function that uses the string with the injections as input and makes a
TemplateResult that can be rendered as DOM.
Usually you’ll have to provide the data to your template so you write your template as a function that can be called multiple times:
myTemplatethat accepts an argument with the values that have to be injected in template.
- Creation of
TemplateResultwith injected values.
Lit-html is a render library and can be used without usage of web components. However, if you do want to develop web components with lit-html, you can use LitElement which is also made by the Polymer team from Google.
- These are the properties that trigger re-rendering and that can be specified as attributes in html tag, like:
- This is the render function that produces
TemplateResultto be rendered to shadow dom
So after reading this, what are hooks? The intro page states the following: “They let you use state and other React features without writing a class”. Indeed now React components can be written using simple functions and not classes like the LitElement example above. Apart from state there are also hooks for component’s lifecycle, context, memoization for complex calculations, reducer and many more. You can also build your own custom hooks.
I am not going to talk about all types of hooks you can create, but I will try to explain the concept via the often used
count is the state value,
setCount is the function that allows you to change the
count state value. The only argument in
useState is the initial state, so in our case by default
count is initialized with
MyComp function is called upon each render and it is up to framework to preserve state between renders. You can also think about this hook in the following way: if
MyComp was a class then
count would be the class field with the change listener for re-rendering.
If you want a detailed explanation of all the hooks you can find it here.
Haunted is a library that provides React Hooks API for standard web components. It uses lit-html as templating engine by default but you can also use other template engines like lighterhtml or hyperHTML.
Haunted is all about writing plain functions instead of classes that can contain their own state using hooks. Here is the simplest hello world example:
As you can see instead of using classes as it is in LitElement you use plain functions here. If you ever worked with React, then you can notice that it looks very similar to their functional components.
LitElement vs Haunted
So lets see how LitElement compares to Haunted. The following web component will show whether your device is offline or online.
You can see above that we defined
properties. We have a constructor where we initialized default values, and we have 2 lifecycle methods
disconnectedCallback to add/remove listeners. We also have
render method for output.
- we define 2 properties:
titleof type Boolean and
onlineof type Boolean
- we assign the default value taken from
- we add an event listener for
- we remove the event listener for
- we render the output to shadow dom
Here is the same functionality with Haunted:
You can see that they are quite similar although the Haunted example has a bit less boilerplate and is written with a complete functional approach.
What are exactly the differences?
- We distinguish if a property is an html attribute differently. In Haunted the values can be injected from attributes only if they are mentioned in
observedAttributes. So in our case
titleis also an html attribute and
onlineis only a state value which is managed by
- The default state value in Haunted can be specified right in
- Instead of using component lifecycle to add window listeners (like
connectedCallbackin LitElement). We can achieve the same with theuseEffect hook with cleanup. The
useEffecthook is used to execute side effects so in the main body of
useEffectfunction we add listeners.
- And in the returned ‘cleanup’ function we remove the listeners (like
- We just return our html template to render the output
CSS can be added in the most obvious way:
Although if you want to use the css helper function from LitElement that’s still possible. For this you would need to add the haunted-lit-element library to your project that adds LitElement features support for Haunted.
In that case, the previous example can be transformed to:
You can see above that
styles can be injected in the
component function as a second argument. Notice that we are now importing the
component wrapper function from haunted-lit-element instead of importing it from Haunted.
Attribute and properties
The property and attribute reflection that comes with LitElement is also supported by Haunted in combination with haunted-lit-element:
As you can see you just have to inject the
properties configuration object, built in the same way as you would do it for LitElement, as the second argument in
And then for the following markup:
you will get the following output:
All the values are going to be converted to the right type with the help of LitElement.
In web components you send a custom event using
dispatchEvent method of the element instance. In Haunted you can get access to the element's instance using the
Notice that in this case you cannot use
arrow functions for your component definition otherwise the
this keyword will not work as it should point to the context of the component function which is the element's instance.
Custom base class
Every web component extends
HtmlElement. For example
LitElement is also based on
HtmlElement. If you use pure Haunted then all your components are based on
HtmlElement and if you use haunted-lit-element then all your components are based on
Sometimes you might want to specify your own base class. For example instead of using component instance while dispatching custom event you can create your own base class that binds the context to
Then while creating the
component you can provide your own base class as a second parameter:
After that you can just destructure your method as a function argument:
And finally our custom event should look a little bit more pure like so:
If an obvious choice for your next project are Web Components, why would you choose to build it also using hooks-based framework?
Popularity of React
Classes vs Functions
In my opinion functions are much easier to follow. You just have one entrance, one exit and logic in between. At the end you also get less boilerplate code. You may have also noticed that Haunted components make your code much more concise.
Mixins vs custom hooks
The current default LitElement’s approach for sharing state between components is based on Mixins. React community already had problems with it in the past. The developers in Facebook have experienced that sharing the code between components using Mixins leads at the end to unmaintainable and confusing code. And react hooks were made exactly as one of the solutions for it.
Imagine that you want to have the same
online functionality that we have built above in other component. Here is the Mixin that you would build for LitElement implementation:
What are the drawbacks with this approach? First of all if you look at the code of MyEl class, the connection between
OnlineMixin and the
online property is not immediately clear, you really have to know it.
And second, if you would need to use more mixins you might potentially have conflicts as all of them can create class instance fields and there is no guarantee that the field name is not already taken.
How would you move out the same functionality in our Haunted element?
As you can see, it couldn’t be simpler. You immediately see the connection, it is very easy to read the code, and there cannot be any conflicts. It doesn’t matter how many hooks you are going to use, as you define the names yourself.
You can share your business logic (hooks) with the components written using a different hooks-based framework. In this case you have to build your hooks so that they can receive
useEffect etc. as parameters so that React or Haunted or any other hooks compatible library could be used:
And then you could use it in a React project like so:
And in a Haunted project you do:
Some code from this article is available in sandbox.
Questions are welcome, feel free to contact me on linkedin.
Thanks for reading!
I want to thank Richard van der Molen for helping me with this article.