@composi/core:

Lifecycle

Component Lifecycle Hooks

With Composi, functional components have three lifecycle hooks that you can access:

Lifecycle hooks let you accomplish tasks specific to the current moment in the component's life. They also let you create animations when the state of a component changes.

onmount

The onmount lifecycle hook gets fired right after the component is mounted in the DOM. It takes a callback whose first argument is the base element of the component. This lets you do some useful things, such as accessing the child elements of the component, or setting up event listeners and delegated events.

In the documentation for props and events we saw how to create a list. Now we are going to look at how to add items to the list. To do that we need to have an input where the user can add a new item, and a button to click to add that item to the list. The onmount hook gives is a way to access the component's input to get its value. So, let's do this. Here's how we make a dynamic list. We've updated it by wrapping the list in a div which has two children, the list and a paragraph with a text input and a button.

Accessing the Component's DOM

In order to access the component's input, we need to add a onmount lifecycle hook. We put it directly on the input. That way the onmount hook gets a reference to the input element. Then we can store that reference on our refs object for easy access in the component's code.

We're also adding onmount hooks onto the list items with the sole purpose of animating them as they are created. Add new list items to see the animation.

See the Pen @composi/core - Lifecycle-1 by Robert Biggs (@rbiggs) on CodePen.

onupdate

Sometimes you may want to know when a component is being updated, like when its props change. You can do that with onupdate. This hook gets three arguments passed to it: the component's base element, the old props and the new props. You can compare any value between the props to see what changed and then do what you need to.

onupdate is only called when the particular element that it is registered on is being updated. The onupdate hook is not envoked when the element is mounted or unmounted.

To be able to use onupdate properly, you need to be aware of what the old and new props are. We pass props to a component when we pass it to the render function:

render(<List data={fruits}/>, 'body')

We call the attribute data a prop. However, in this case it will not persist as a prop. That's because the render function will convert the tag into a function and its props will be pass to the function as arguments. Technically, props are only the values directly attached to the elements that the component function returns. Let's take another look at a List component:

function List({data}) {
  function logUpdate(element, oldProps, newProps) {
    // compare oldProps with newProps...
  }
  return (
    <ul onupdate={logUpdate}>
      {
        data.map(item => <li key={item.key}>{item.value}</li>)
      }
    </ul>
  )
}
render(<List data={fruits}/>, 'body')

In the above example, notice that we've place the onupdate event directly on the component's base element. Then in our logUpdate function we want to compare the old and new props to see what changes. You might be thinking that we will get the old and new versions of the data prop. Now look at the element. Do you see any props on it besides onupdate? That's the problem. The ul has no props. The fruits data that we want to test is coming in as a parameter and we are using it separately in the script block delimited by curly braces. This means that when we try to access newProps and oldPros, these will be undefined. To enable the onupdate lifecycle event to access the data, we need to register that data as a prop directly on the element were the lifecycle hook is. Here's the component updated:

function List({data}) {
  function logUpdate(element, oldProps, newProps) {
    // compare oldProps with newProps...
  }
  // Attach the data directly on the ul tag:
  return (
    <ul onupdate={logUpdate} data={data}>
      {
        data.map(item => <li item={item.key}>{item.value}</li>)
      }
    </ul>
  )
}

This subtle change in exposing the data to the update event means that now we have access to the previous and current state of data. That means we could compare oldProps.data with newProps.data to see what changed.

Always make sure you are exposing the properties you need to on the same element where onupdate is placed. Otherwise you're values will be undefined.

In this example we show how to use onupdate to log out the differences between the count value as it increases and decrease. By using oldProps and newProps, we can see how the value changes and log the difference.

See the Pen @composi/core - update-example by Robert Biggs (@rbiggs) on CodePen.

You might be wondering when you would use update. The use case for it is very limited. It's great if you want to see what props changed, as we did above to log out the changes. You would not use it to change data, as that would trigger another update and put you in an infinite loop. You might use it to add a class that causes an animation, and then remove the class. You could also use it to set focus on an input or other form element.

onunmount

Sometimes when a component is being removed from the DOM you want to be able to know so you can do something. Maybe you need to do some environmental cleanup, or trigger some other event or function. onunmount lets you do that. In fact, onunmount lets you intercept the unmounting process and delay it until you are done with whatever it is you need to do. This is great for situations where you want to do an animation when a list item is delete. We'll make a simple list that uses onunmout to animate the list item before deleting it. For this, we'll leave out the part that actually deletes a list item since we already covered that in the example for event delegation above.

onunmount expects that its callback will handle a done() function. Specifically, onunmount gets passed two arguments: the element being deleted, and a done callback to invoke when you are done. If you fail to provide a done() callback in your code or forget to invoke at the end of your code, the element will not be removed from the DOM, which could lead to really unexpected results as the list data is mutated.

In the following example we're using onunmount to run a keyframe animation before the list item is removed. We've also got an onmount on the list items to enable animating new list iems into view.

See the Pen @composi/core - Lifecycle-onunmount by Robert Biggs (@rbiggs) on CodePen.

Lifecycle Event Arguments

The lifecycle events get their arguments automatically, so you can just use their plain handlers in the inline event. Each lifecycle event expects different arguments, but you indicate these in the handler, on in the inline event:

function List() {
  function init(base) {
    // Do something with the component's base element.
    base.querySelector('input').focus()
  }
  function logUpdate(element, oldProps, newProps) {
    // Element the onupdate event is on, in this case, the ul tag.
    // Compare the oldProp values with the new.
    if (newProps.data.length > oldProps.data.length) {
      console.log('An item was added to the list.')
    } else {
      console.log('An item was deleted from the list.')
    }
  }
  function handleUnmount(element, done) {
    // Do something with the component base element.
    // Trigger deletion animation:
    element.classList.add('deleting')
    // Then let the component unmount.
    // Since we've set the animation to last 3 seconds,
    // we need to delay the unmounting for 3 seconds:
    setTimeout(() => {
      done()
    }, 3000)
  }
  return (
    <ul onmount={init} onupdate={logUpdate} onunmount={handleUnmount} data={data}></ul>
  )
}

The thing to be aware about onunmount, it causes the unmounting to be asynchronous. Even though the patch algorithm has identified a condition that requires the element to be removed from the DOM, it waits to actually do so until you fire done() in your onunmount handler.