Say goodbye to lifecycle methods, and focus on productive code
Tags: webdev, javascript, webcomponents, functional
Published: 2019-01-29 09:28:42

---
title: Say goodbye to lifecycle methods, and focus on productive code
published: true
description: Lifecycle methods pushed us to think more about *when* something happens rather than to define *how* we can get *what* we need. What would you say if you could focus on value computations and leave the rest to the library?
tags: webdev, javascript, webcomponents, functional
cover_image: https://thepracticaldev.s3.amazonaws.com/i/5ca3isoqa7crrs1zmic9.jpg
series: Core Concepts of the Hybrids Library
---

*This is the second in a series of posts about core concepts of [hybrids](https://github.com/hybridsjs/hybrids) - a library for creating Web Components with simple and functional API.*

One of the most rooted features of component-based UI libraries is a complex lifecycle. It is a group of methods, which provide full control over the state of the component that may change over time. Usually, libraries use self-explaining name convention and call did* methods after something happens and will* before the change. While studying the library docs, we often find a whole range of possibilities, which can lead to confusion or even frustration. After all, you need to have an in-depth understanding to create correct and efficient code. For example, the component state may depend on a specific sequence of events in time, which makes the code hard to test and eventually maintain or extend.

## Is it so bad?

Let's face it two obscure facts about lifecycle methods. Firstly, they shift the burden of state management from the library to us. As it might look legit, it usually means, that we have to write more redundant code manually:

```javascript
class MyComponent extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.name !== prevProps.name) {
      // do something...
    }
  }
}
```

In the above example, the library provides a map of previous properties, but it doesn't inform which of them has a new value. We have to create conditions explicitly to be sure that our code is called only if the `name` property has changed.

In another hand, if a component requires asynchronous data, lifecycle structure may force to fetch data twice - for the first time in something like `componentDidMount()` method, and then each time in `componentDidUpdate()` when the dependencies change:

```javascript
import { getUser } from './api';

class MyComponent extends Component {
  componentDidMount() {
    this.fetch();
  }

  componentDidUpdate(prevProps) {
    if (this.props.userId !== prevProps.userId) {
      this.fetch();
    }
  }

  fetch() {
    getUser(this.props.userId)
      .then((data) => this.setState({ data }));
  }
}
```

Even though we have extracted redundant logic into the `fetch()` method, it has to be called twice in two separate lifecycle methods. 

Both code examples might look familiar to you. In fact, they represent what the `React.Component` class provides. React of course is not a web components library, but [LitElement](https://lit-element.polymer-project.org/guide/lifecycle), [Omi](https://github.com/Tencent/omi#lifecycle), [Slim.js](http://slimjs.com/#/component-lifecycle), [Stencil](https://stenciljs.com/docs/component-lifecycle) and many others follow the trends, and they implemented very similar concepts (use the links to go to the lifecycle section of libraries documentation).

In the first post of the series, we have learned how we can switch component definition from the class syntax into the map of independent property descriptors. If you haven't read it yet, it's a good moment to do so:

{% link smalluban/from-classes-to-plain-objects-and-pure-functions-2gip %}

This time we will go deeper into the property descriptor definition and learn more about cache mechanism, change detection and its `connect` method.

## Different approach

Lifecycle methods pushed us to think more about *when* something happens rather than to define *how* we can get *what* we need. What would you say if you could focus on value computations and leave the rest to the library? 

The hybrids property descriptors concept introduced much more than only a middleware for holding property value. The library provides a complete cache and change detection mechanism.

A component, which requires data fetched asynchronously can be defined with hybrids just like that:

```javascript
import { html } from 'hybrids';
import { getUser } from './api';

const AsyncUser = {
  userId: 1,
  data: ({ userId }) => getUser(userId),
  render: ({ data }) => html`
    <div>
      ${html.resolve(
        data.then(user => html`
          <span>${user.firstName}</span>
        `),
      )}
    </div>
  `,
};
```

[Click here to play with a live example on ⚡️StackBlitz](https://stackblitz.com/edit/hybrids-async-user?file=async-user.js)

The above definition includes `userId`, `data` and `render` descriptors. The `data` property depends on `userId` and returns a promise with user details. Don't bother much about the `render` property for now. You should need to know now that it uses under the hood the `render` factory (using property translation), which uses `html` function to create and update contents of the custom element. In the body of the template, we are using dynamic value, which resolves `data` promise to an element with the first name of the user.

## Cache mechanism

The cache mechanism is attached to the getter and setter of every property defined by the library. For `set` method it automatically updates the cache if calculation returns a new value. For `get` method cache ensures that the value is only computed if needed, for example, when one of the property dependency has changed. In our example, it means, that `getUser()` will be called to set an initial value and only when `userId` will change. How does it work?

The cache controls the `data`, as well as `userId` property. When `userId` is called inside of the `data` getter, the cache can save it as a `data` dependency. Next time, when we call `data`, cache checks `userId` from the cache and calls `getUser(userId)` only if `userId` has changed. Otherwise, it returns the last cached value and omits getter. The cache is global for all elements defined by the library so we can depend on properties defined in other elements too!

The cache concept uses the fact that properties are never computed if they are not called (even if the dependencies have changed). You could try to get a value of `data` manually, and you would see, that it returns the same promise all the time. However, if you change `userId` property, `data` will return a new promise called next time.

## Simplified lifecycle

In the first post, we have learned that the property descriptor may have `get` and `set` methods. Actually, you can define two more for property lifecycle control - `connect` and `observe` method. `connect` method can return a function, which is called when an element is disconnected. While the `observe` method is called asynchronously when the property value changes.

```javascript
{
  get: (host, lastValue) => {...},
  set: (host, value, lastValue) => {...},
  connect: (host, key, invalidate) => {
    // ...
    return () => {...}; // disconnect
  },
  observe: (host, value, lastValue) => {...},
};
```

However, in the above `AsyncUser` example we didn't have to use it explicitly. We even didn't have to create property descriptors at all! If we would take all the concepts together, we may start to see a bigger picture here. The raw descriptor provides all the required features to create stateful properties. Then the library adds on top of that cache mechanism. However, the preferred way to define properties is to use built-in or custom factories (functions, that produce descriptors). As the property definition is independent, you can re-use factories wherever you want. As the result, you don't have to define `connect` method by yourself, and you can focus on productive coding in a declarative way!

## Invalidation

You may have noticed a third argument of the `connect` method - `invalidate` callback. If a property has only a getter, but it depends on third-party tools, `invalidate` is a clever way to notify cache, that value should be computed next time. Because of the functional structure, it is super easy to create properties connected to external state managers like redux:

```javascript
import store from './store';

function connect(store, mapState) {
  return {
    get: (host) => mapState(store.getState(), host),
    connect: (host, key, invalidate) => store.subscribe(invalidate),
  };
};
```

Redux `subscribe` method takes a callback where we can pass `invalidate`. It returns unsubscribe function so we can call it in the connect method defined as an arrow function. We can use the factory in the component definition, like in the following example:

```javascript
import store from './store';
import connect from './connectFactory';

const MyElement = {
  userId: 1,
  userDetails: connect(store, ({ users }, { userId }) => users[userId]),
};
```

## Change detection mechanism

In the last part of the post let's go back to `render` property. If the library does not call getters for us, how is it possible that our component works? Even though `render` might look special, is it the same property descriptor as the rest. The difference is in how the `render` factory uses `connect` and `observe` methods.

The best way to understand how `render` works is to built a simplified version:

```javascript
function render(fn) {
  return {
    get: (host) => fn(host),
    connect: (host, key) => {
      if (!host.shadowRoot) host.attachShadow({ mode: 'open' });
    },
    observe: (host, fn) {
      fn(host, host.shadowRoot);
    },
  };
}
```

Our `render` factory returns descriptor with `get`, `connect` and `observe` methods. We took advantage of the cache mechanism, so our getter calls `fn` and saves its dependencies. The property value will be only recalculated if one of the properties used in the `fn` changes.

The `connect` creates `shadowRoot` if it is not already there. Then we want to call `fn` whenever dependencies change. It is exactly what `observe` method provides. It might looks familiar to `componentDidUpdate()` callbacks from other libraries. Eventually, we want to do something when the change occurs. However, the idea behind the `observe` method is much deeper. The library calls it only when the value of the property has changed. This method is also called only once during the current event loop, because of the internal queue scheduled with `requestAnimationFrame` API. We don't have to bother to check what property has a new value or not because we covered it with the cache mechanism. 

## Summary

It might be a lot of new stuff to process. For sure, hybrids didn't give up on lifecycle methods. They are just redesigned and implemented in the opposite direction to patterns known from other libraries. In the explained component example, the chain of cause and effect goes from render property to data (in other libraries it would go from fetching data to rendering new state). A function, which creates a template, wants user details, and only because of that they are fetched, and they eventually trigger an update of the template. If in some condition the template would not require those data, they would not be fetched at all.

We can call it **simplified lifecycle**. If we add on top of that smart cache mechanism and all already known property-based concepts, it changes everything. We can shift the most of state-related responsibility to the library and focus on the business logic of our components. Usually, the component requires a list of properties for holding simple or computed values and render method for creating element structure. If we need something not covered by the library, we can easily create reusable factories and still do not use lifecycle methods directly.

## What's next?

Today, we have scratched the surface of the `render` factory. In the next post of the series, we will learn more about render factory provided by the library, as well as the rich template engine built on top of tagged template literals.

In the meantime, you can read more about the hybrids library at the [project documentation](https://hybrids.js.org).

{% github https://github.com/hybridsjs/hybrids %}

---------

🙏 **How can you support the project?** Give the GitHub repository a ⭐️, comment below ⬇️ and spread the news about hybrids to the world 📢!

---------
👋 **Welcome dev.to community!** My name is Dominik, and this is my third blog post ever written - any kind of feedback is welcome ❤️.

<small>Cover photo by [Paul Skorupskas](https://unsplash.com/photos/7KLa-xLbSXA) on [Unsplash](https://unsplash.com)</small>