Functional Component

The component architecture is based on classes. If you favor functional programing over OOP, you might prefer to use functional components. Functional Components are always stateless, so you will want to use them with some kind of state management, such as Redux, Mobx, etc.

Virtual Dom

When you create components with the Component class, you have a reference for the component's properties, methods, etc. This also provides a point of reference for the virtual DOM. In contrast, functional components do not have this reference for their virtual DOM. Instead, with functional components, the scope in which the function is invoked becomes the scope for the virtual DOM. This means as your project grows and code gets out over many files, the scope of a functional component can be spread across several files. For components this is not a problem. For functional components this means the first time it gets invoked in a new scope, the previous virtual DOM will be replaced by a new one. Unless you are creating a list with 10,000 or more items, you won't notice any inefficencies. However, it does result in more layout thrashing than when creating class-based components.

Creating Functional Components

Functional components use JSX to create markup, but technically you could also use [Hyperx-ES6](https://www.npmjs.com/package/hyperx-es6) with ES6 template literals. Here we're going to look at using JSX.

To create a functional component, you start with a function, surprise! The function could accept some data, or not. It depends on what the function needs to return. If it is returning static content, no need for a parameter of data. If you want the function component to consume some data, then pass it in as a parameter. And of course you'll need to return some markup. In the example below we have a simple, function component that creates a header:

// title.js:
// We need to import the "h" function:
import { h } from 'composi'

// Define function that takes props for data:
export function Title(props) {
  // Return the header with name from props:
  return (
    <nav>
      <h1>Hello, {props.name}!</h1>
    </nav>
  )
}

If we were to do this with the Composi h function, it would look like this:

import { h } from 'composi'
import { html } from 'hyperscript'

// Define function that takes props for data:
export function Title(name) {
  return h(
    'nav', {}, h(
      'h1', {}, name
    )
  )
}

Both examples above create virtual nodes, so we will need a way to get them into the DOM. Composi provides the mount function for that purpose. It works similar to React's ReactDOM.render function. It takes two arguments: a tag/vnode and a selector/element in which to insert the markup.

In our app.js file we import h and mount and then the Title functional component and insert it into the DOM:

// app.js:
import { h, mount } from 'composi'
import { Title } from './components/title'

// Define data for component:
const name = 'World'

// Inject the component into header tag.
// We pass the name variable in as a property in curly braces:
mount(<Title {...{name}}/>, 'header')

Codepen Example:

See the Pen Composi functional-components-1 by Robert Biggs (@rbiggs) on CodePen.

This will convert the functional component into a virtual node (vnode) and then convert that into actual nodes inside the header tag. Because a virtual node was created, we can re-render this later using the render function with new data and Composi will patch the DOM efficiently for us. In this case that would involve patching the text node of the h1 tag.

List with Map

Now lets create a functional component that takes an array and outputs a list of items. Notice that we are using BEM notation for class names to make styling easier.

// list.js:
import { h } from 'composi'

export function List(props) {
  return (
    <div class='container--list'>
      <ul class='list--fruits'>
        {props.items.map(item => <li class='list--fruits__item'>{props.item}</li>)}
      </ul> 
    </div>
  )
}

This list is consuming an items array:

// items.js:
export const items = ['Apples', 'Oranges', 'Bananas']

In our `app.js` file we put this all together:

// app.js:
import { h, mount } from 'composi'
import { Title } from './components/title'
import { List } from './components/list'
import { items } from './items'

// Define data for Title component:
const name = 'World'

// Insert component into header:
mount(<Title {...{name}}/>, 'header')

// Insert list component into section with items data:
mount(<List {...{items}}/>, 'section')

Codepen Example:

See the Pen Composi functional-components-2 by Robert Biggs (@rbiggs) on CodePen.

Custom Tags

We can break this list down a bit using a custom tag for the list item. We will need to pass the data down to the child component through its props:

// list.js:
import { h } from 'composi'

function ListItem(props) {
  return (
    <li class='list--fruits__item'>{props.item}</li>
  )
}

export function List(props) {
  return (
    <div class='container--list'>
      <ul class='list--fruits'>
        {props.items.map(item => <ListItem {...{item}}/>)}
      </ul> 
    </div>
  )
}

Codepen Example:

See the Pen Composi functional-components-3 by Robert Biggs (@rbiggs) on CodePen.

Data Flows Down

When using custom tags inside a functional component, the data flows down, from the parent to the children. There is no two-way data binding. This makes it easy to reason about. If you need a child component to communicate some kind of data change to its parent, you can use events or any pubsub solution from NPM. We have a tiny and efficient pubsub module on NPM called pubber that is perfect for these situations.

Events

What if we wanted to make this list dynamic by allowing the user to add items? For that we need events. We'll add a text input and button so the user can enter an item. Since this doesn't involve any data consuming any data, their function just needs to return the markup for the nodes to be created. We'll call this function component ListForm:

// list.js:
import { h } from 'composi'

function ListForm() {
  return (
    <p>
      <input class='list--fruits__input--add' placeholder='Enter Item...' type="text"/>
      <button class='list--fruits__button--add'>Add Item</button>
    </p>
  )
}
function ListItem(props) {
  return (
    <li>{props.item}</li>
  )
}

export function List(props) {
  return (
    <div class='container--list'>
      <ListForm />
      <ul>
        {props.items.map(item => <ListItem {...{item}}/>)}
      </ul> 
    </div>
  )
}

handleEvent Interface

We are going to use the handleEvent interface to wire up events for the component. We'll do this in a separate file and import it into our app.js file. To use handleEvent with a functional component we'll need to create an object with the handleEvent function on it. Then we'll use a standar event listener and pass the that object instead of a callback.

Since we want to be able to add a new fruit to the list, we need to have access to the data and also the function component that renders the list. Therefore we add events object to handle that.

Notice how we assign the result of mounting the functional component to the variable list and use that as the secton argument of the render function to update the list in the addItem function in the events object:

import { h, mount, render } from 'composi'
  function ListForm() {
  return (
    <p>
      <input class='list-fruits__input-add' placeholder='Enter Item...' type="text"/>
      <button class='list-fruits__button-add'>Add Item</button>
    </p>
  )
}
function ListItem(props) {
  return (
    <li class='list-fruit__item'>{props.item}</li>
  )
}

function List(props) {
  return (
    <div class='container-list'>
      <ListForm />
      <ul class='list-fruit'>
        {props.items.map(item => <ListItem {...{item}}/>)}
      </ul> 
    </div>
  )
}

const items = ['Apples', 'Cats', 'Hats']

const events = {
  addItem(e) {
    const input = document.querySelector('.list-fruits__input-add')
    const value = input.value
    if (value) {
      items.push(value)
      // Pass in "list" variable from mounting:
      list = render(list, <List {...{items}}/>, 'section')
      input.value = ''
    } else {
      alert('Please provide an item before submitting!')
    }
  },
  handleEvent(e) {
    e.target.className === 'list-fruits__button-add' && this.addItem(e)
  }
}

const list = mount(<List {...{items}}/>, 'section')

document.querySelector('.container-list').addEventListener('click', events)

As you can see above, we need to get the value of the input. If the value is truthy, we push it to the items array. Then we re-render the functional component list. Because this is a different scope than app.js where we initially rendered the list, the first time we add an item, the list will be created a new, replacing what was there. That's because this is a different scope than app.js. However, with every new addition, the render function will use the new virtual DOM in this scope to patch the DOM efficiently.

Using the handleEvent Object

Now that we have our handleEvent object defined, we need to wire it up to our component. We'll do that in our app.js file:

Codepen Example:

See the Pen Composi functional-components-4 by Robert Biggs (@rbiggs) on CodePen.

And that's it. With that we now have an interactive functional component that updates in real time with a virtual DOM.

Inline Events

We could also create a functional component with inline events. We're going to take our interactive list from above and take it to the next level. We'll let the user also delete items. We'll considate all the sub-components into one. We'll also add in private event handlers for the inline events. We've bundled this all up as a Codepen example:

See the Pen Composi Functional Inline Events by Robert Biggs (@rbiggs) on CodePen.

Lifecycle Hooks

Functional components have three lifecycle hooks:

  • onmount
  • onupdate
  • onunmount

Using Lifecycle Hooks

To use lifecycle hooks on a functional component you need to set them up as a property on the component with a function for the action to be executed when the hook is called. Notice how we attach the onmount lifecycle hook to the UL element in the List function:

// Define event handler for list:
const listEvent = {
  handleEvent(e) {
    e.target.nodeName === 'LI' && alertItem(e)
  },
  alertItem(e) {
    alert(`You chose: ${e.target.textContent.trim()}.`)
  }
}

// Function to attach event after mounting:
function setupListEvent(element) {
  // Use event handler object:
  element.addEventListener('click', listEvent)
}

// Functional component to create list:
function List({items}) {
  return (
    // Attach onmount lifecycle hook:
    <ul class="list" onmount={(element) => setupListEvent(element)}}>
      {
        data.map(item => <li>{item}</li>)
      }
    </ul>
  )
}

Lifecycle Hook Arguments

The onmount callback gets passed the base element of the container as its argument. This is the same as Component.element in a class-based component. You can use this to attach events, or query the component DOM tree.

The onupdate callback gets passed the element, the old props and the new props. You can compare the old and new props to find out what changed.

onunmount fires before the component has been removed from the DOM. It gets passed a reference to the element on which it is registered and a done function. You can use this lifecycle hook to do some environmental cleanup, or animate the element before the component is deleted. You can use this lifecycle hook on a functional component's children, such a list items of a list. The component will not be removed from the DOM until you call the done function. In the code sample below, notice how in the removeEl function we initialize a CSS animation to last half a second. The we set up a timeout to last half a second, afterwhich we run the done function. This delays the removal of the list item until after the animation finishes.

function removeEl(el, done) { 
  el.style.cssText = 'transition: all .5s ease-out; height: 0px; transform: translateX(-300px);'
  // Set timeout for half a second, matching the animation above, 
  // then fire `done` function:
  setTimeout(function() { 
    done() 
  }, 500)
  
}
function List() {
  return (
    <ul className="list"> { props.data.map(item => (
      <li class='list-item' key={item.key} onunmount={(el, done) => removeEl(el, done)}>
        <span>{item.value}</span>
        <button data-id={item.key} class='delete-item' onclick={e => deleteItem(e)}>X</button>
      </li>
      )) }
    </ul>
  )
}

Memoization Avoids Unncessary Updates

When you use the update lifecycle hook, it will always fire when you do a render, even if the data being use has not changed. This does not mean that the DOM was physically updated. That would only happen if the props changed. Every time you render a functional component with update on it, update will execute. There is only one way to get around this--memoization.

Below is a Codepen with a list created as a function component. The list items have an onupdate lifecycle hook. When you click on the button that says "Render List with Same Data", it re-renders the list. Notice how in the results you see and oupt for each list item. This is so event though there was no change to the data the list is using. This means there was no physical update of the list items. This is because of how the diffing algorithm works.

Normal List without Memoization

Here's an example of the problem. Notice that as you add items to the list, all the already existing items get output as updated. Also notice that every time you tap the "Render List with Same Data" button, all the items are output as updated, event those you rendered the list with the same data. Infact, because the data was the same, the actual DOM elements would not have been updated.

See the Pen Functional Component with Update by Robert Biggs (@rbiggs) on CodePen.

Memoization Solves Update Issues

We can get around this problem by memoizing the list items. This is a little more work than simply outputting a list, but it gives us the desired result--list items will indeed fire the update lifecycle hook when their data changes. Otherwise you can render the list with the same data again and again and the update hook will not fire.

We use an array, cachedLisItems on the appVar object to store the memoized list items. Notice how in the render function we store the list items in cachedLisItems and that we use those values to output the list items. This gives us a source for the list items outside of the function components diffing process.

Example of Memoized List

See the Pen Functional Component with Memoization by Robert Biggs (@rbiggs) on CodePen.

The above technique solves the problem of the update lifecycle hook firing every time you render a function, even when the data is the same. However, be aware that this increases the memory that the list is using. If your list can be long, and you use this technique on other long list, you may find that your app is consuming a lot of memory, which can lead to other problems. If memoizing list items seems like too much trouble, consider making your list component using the Component class. Class components memoize their previous state so that their update lifecycle hooks only fire when their data changes.

Component Class Advantages

Although you can build a complex app using nothing but functional components, there are certain conveniences when using the Component class. First and foremost is the fact that classes allow you to encapsulate functionality using properties and methods defined directly on the component. The second is the component itself enables more efficient use of the virtual DOM because it keeps track of that internally. Because of this, you can have multiple class components in the same container. Class components don't have the problem with updates like we saw in functional components that needed memoization. This is because class components automatically memoize their previous state on the component instance. That way a component instance can accurately tell whether it should fire its componentDidUpdate lifecycle hook.