API

Composi is only 3KB. That means the API surface is fairly small. Composi exposes four functions (h, render, createElement and Fragment) and one class (Component) with four properties, four methods and five life cycle hooks.

Optional Installs

There are several optionals classes that you can import into your project. These are separate from the standard import of Composi. To import them you have to access a sub-folder of Composi: data-store. In the following example, notice how we use /data-store after composi. This is so we can reach the folder where these optional classes are stored.

import { DataStore, DataStoreComponent, Observer, uuid } from 'composi/data-store

h

The h function serves two purposes. Firstly it gets used by Babel during build to convert JSX into a virtual node that Composi can use for diffing and patching the dom. Secondly it provides a way to define component markup without using JSX.

h takes three arguments:

  • type - The type of node to create. This is indicated by providing a lowercase tag name: "ul", "li", "div", etc.
  • props - An object literal of properties and their values: {class: 'list-users'}
  • children - Either a simple string for a text node, or an array of other h definitions: 'Hello' or [h('li')]

To create a node you only really have to provide a type. If an element has no properties you can use and empty object: {} or null. Same for children:

const list = h(
  // type:
  'ul',
  // props:
  {},
  // children:
  null
)
// or:
const list = h(
  // type:
  'ul',
  // props:
  null,
  // children:
  null
)
// or:
const list = h(
  // type:
  'ul'
)

If you are creating a node that doesn't have properties but does have children, you will need to provide either {} or null or props:

const list = h(
  // type:
  'ul',
  // props:
  {},
  // children:
  [
    h(
      // type:
      'li',
      // props:
      {},
      // child:
      'Apple'
    ),
    h(
      // type:
      'li',
      // props:
      {},
      // child:
      'Orange'
    )
  ]
)

Of course, you would not be putting those comments in your code. That was just to make it clear how the h call signature works.

mount

When making functional components you use the mount function to inject the component into the DOM for the first time. After that, if you need to update it, you would use the render function described next.

The mount function returns a virtual node representing the mounted component. If you intend to update the component with the render function, you need to capture that reference in a variable so you can pass it to the render function.

The mount function takes two arguments: the tag to create and the container in which to insert the component. Notice how we do this:

import { h, mount } from 'composi'

function Title({message}) {
  return (
    <nav>
      <h1>{message}</h1>
    </nav>
  )
}

// Mount the component in the header tag:
mount(<Title message='Hello World!'/>, 'header')

Hydration with mount

You can use the mount function to hydrate server-rendered markup with a functional component. To do so, just provide a third argument to the mount function, which should reference the element in the DOM tree to hydrate. This can be a DOM node or of a valid CSS selector for the node.

// Markup in html from server:<h1 id='special-title'>The Old Title</h1>

// Functional component for new title:
function Title({content}) {
  return (
    <h1 id='special-title'>{content}</h1>
  )
}

// Hydrate server makup with new component:
mount(<Title content='The New Title' />, 'header', '#special-title')

In the above markup, the second argument is the container in which to render the functional component. The third argument refers to a node in the container's DOM that the functional component should update. In the example above the DOM node target and the functional component are the same node type and have the same ID. This is not required. Composi takes the third argument, accesses the element in the DOM, creates a virtual node from that and uses it to patch the DOM. This allows the functional component to add events and dynamic behavior to server-rendered markup with minimal DOM manipulation, improving initial load time and time to interactivity.

render

The render function allows you to update functional components already mounte in the DOM. It takes two arguments:

  • tag: Either a JSX tag, and h function or a virtual node.
  • vnode: The virtual node returned by the mount function.
  • container: The element in which the component was mounted.

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) {
  function init(el) {
    el.addEventListener('click', events)
  }
  return (
    <div class='container-list' onmount={el => init(el)}>
      <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.
      // Capture back into that variable for rendering later.
      list = render(<List {...{items}}/>, list, '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')


Fragment

Composi provides a Fragment tag to let you return multiple siblings without a wrapper tag. When Fragement is parsed by Composi, only the children are returned. This is useful for creating lists and tables.

import { h, Component, Fragment } from 'composi'

function ListItems({data}) {
  return (
    <Fragment>
      {
        data.map(item => <li>{item}</li>)
      }
    </Fragment>
  )
}

class List extends Component {
  render(data) {
    return (
      <ul class="list">
        <Fragment data={data}/>
      </ul>
    )
  }
}

new List({
  container: 'section',
  state: todos
})

Please note that Fragment tags cannot be inserted directly into the DOM. The Fragment tag does not create a document fragment. It is a functional component that returns its children for inclusion in another virtual node.

Component

The component class is the main part of Composi that you will be using. This class provides a number of properties and methods for you to use.

Extending Component

You create a new component definition by extend the Component class. This lets you create custom properties and methods for your component based on what it needs to do. When you extend Component, every class method with have access to the class properties and methods through its this keyword

In the following example, notice how we added the properties state and key to the class constructor. Because we are adding properties to the contstructor, we all need to include super and pass it props. We also added a render function, inside of which we can easily access the component's state property. We also added a method called handleEvent to implement events. And finally a componentDidMount hook to set up the main event. And finally, we use are class by instantiating it and passing it a value for the container to render in.

class List extends Component {
  constructor(props) {
    super(propss)
    this.state = fruits
    // key to use for adding new items:
    this.key = 1000
  }
  render() {
    let state = this.state
    return (
      <div>
        <p>
          <input id='nameInput' type='text' />
          <button id='addItem'>Add</button>
        </p>
        <ul id='fruitList'>
          {
            this.state.map(fruit => <li key={fruit.key}>{fruit.name}</li>)
          }
        </ul>
      </div>
    )
  }
  handleEvent(e) {
    // Handle button click:
    if (e.target.id === 'addItem') {
      const nameInput = this.element.querySelector('#nameInput')
      const name = nameInput.value
      if (!name) {
        alert('Please provide a name!')
        return
      }
      this.setState({name, key: this.key++}, this.state.length)
      nameInput.value = ''
      nameInput.focus()
    // Handle list item click:
    } else if (e.target.nodeName === 'LI') {
      alert(e.target.textContent.trim())
    }
  }
  componentDidMount() {
    this.element.addEventListener('click', this)
  }
}
const list = new List({
  container: 'section'
})

 

Component - container

This is a property on the component that is the base element of the component. Many components can share the same container, for example, you might have them rendered directly in the body tag. This means that the contain is not the component. When you access the component from its element property, you are one level down from the container.

const list = new List({
  container: 'section'
})

Notice:

Once you instantiate a component, you cannot change its container. You can change the value of the component's selector or even its container value, but these changes will not affect the container currently used by the component when rendering. During initialization the component tries to get the selector provided. If the selector cannot be found in the DOM, document.body will be used as the container. Whatever container the component gets during intialization will remain in effective for the duration of the current session. This can only be changed by a page reload.

Component - render

This is a method defined on a component that defines the markup that the component will create. It returns a virtual node when it executes. By default when this is executed it checks for state to use. You can bypass this by passing data through the component's update method.

class List extends Component {
  render(data) {
    return (
      <ul>
        {
          data.map(item => <li>{item}</li>)
        }
      </ul>
    )
  }
}

Component - update

If you have a component that does not have state, after initializing it you'll ned to execute udpate on it. Doing so will cause it to be rendered in the DOM. You can also optionally pass data to the update method. Doing so allows you to bypass the current state of the component.

class Title extends Component {
  render(message) {
    return (
      

Hello, {message}!

) } } const title = new Title({ container: 'header' }) // Update the component: title.update('Harry Potter')

Component - state

Components can be stateless or statefull. You use the state property to set that up. Once you have given your component state, you should use the setState method to update it. When you assign a value to a component's state property, this causes the component to be rendered to the DOM automatically. This is because the Component class has getters and setters for state. Whenever the setter is invoked it also invokes the component's update method, resulting in a render or patching of the component in the DOM.

// Define container and state in constructor:
class Title extends Component {
  constructor(props) {
    super(props)
    this.container = 'header'
    this.state = 'Harry Potter'
  }
  render(message) {
    return (
      

Hello, {message}!

) } } const title = new Title() //or define container and state during initialization: class Title extends Component { render(message) { return (

Hello, {message}!

) } } const title = new Title({ container: 'header', state: 'Harry Potter' })

Component - setState

This method lets you update the state of a component. When the compoent's state is an object or array, this lets you perform partial updates on the state, changing a property value of array index value. Internally, setState invokes the state setter, which cause the component to be updated.

class Title extends Component {
  render(message) {
    return (
      

Hello, {message}!

) } } const title = new Title({ container: 'header' }) // Set the state with setState: title.setState('Harry Potter')

Component - unmount

If you want to destroy a component, removing it from the DOM, you will use this method. It gets executed on the component instance. unmount deletes the component base element from the DOM and nulls out all its properties on its instance for garbage collection. Before version 5.2.2, Composi would try to unbind a whitelist of events from the component base before unmounting. Now, before unmounting you need to remove any event listeners yourself.

// Create component instance:
const title = new Title({
  container: 'header',
  state: 'Harry Potter'
})
// Sometime later destory the component:
title.unmount()

Component - element

Sometimes you need an easy way to reach child elements in a compnent, either to get a form input value or to register and event. You can do this through the component's element property. element is the base or topmost element of your component's markup. This is not the same as a component's container, which is the element into which a component gets rendered. Many components can exist inside the same container, such as the body tag.

Component - componentShouldUpdate

This property determines whether Composi should re-render a component when its state changes or its update method is invoked. By default it is set to true. Setting it to false will allow the component to render initially but ignore any other updates. As soon as the property is set back to true, the component will begin updating again.

This is useful for situations where you need to pause the update of a component while you perform some complex operations.

class Hello extends Component {
  constructor(props) {
    super(props)
    this.container = 'header',
    this.state = 'World'
    this.componentShouldUpdate = false
  }
  render(data) => {
    return (
      <h1>Hello, {data ? `: ${data}`: ''}!</h1>
    )
  }
}
// Create instance of Hello:
const hello = new Hello()

// Some time later update the component's state:
hello.setState('Joe')
// Because componentShouldUpdate is false, the component will not update.

// Some time later set componentShouldUpdate to true:
hello.componentShouldUpdate = true
hello.setState('Joe')
// Now the component updates.

Lifecycle Hooks

Lifecycle hooks allow you to do things at different times during the existence of a component. Composi has five. These are asynchronous. That means that the event may finish before the hook can. This is probably most important when deal with unmounting. If you have a lot of cleanup to do, do it before unmounting. The most useful hook is componentDidMount.

Component - componentWillMount

This fires right before the component is rendered and inserted into the DOM. This gets passed a done callback which you call when you finish whatever you were doing:

componentWillMount(done) {
  // Do whatever you need to do here...
  // The let the mount happen:
  done()
}

If you fail to call done() inside your componentWillMount funciton, the component will never mount!

Component - componentDidMount

This fires right after the component has been injected into the DOM. You can use this to set up events for the component or start a interval loop for some purpose. You can access the base element of the component from within this lifecycle hook. You do this by using the component's element property. Notice how we do this to focus on an input:

// Set focus on an input:
componentDidMount() {
  // Use the component base to focus on the input:
  this.element.querySelector('input').focus()
}

Component - componentWillUpdate

This fires right before the component is updated. Updates happen when the update method is invoked, or the state is modified. This gets passed a done callback which you call when you finish whatever you were doing:

componentWillUpdate(done) {
  // Do whatever you need to do here...
  // The let the update happen:
  done()
}

Component - componentDidUpdate

This fires right after the component was updated. Updates happen when the update method is invoked, or the state is modified.

Component - componentWillUnmount

This fires when the unmount method is invoked on the component instance. This gets passed a done callback which you call when you finish whatever you were doing:

componentWillUnmount(done) {
  // Do whatever you need to do here...
  // The let the unmount happen:
  done()
}

innerHTML

This is a property which you can set on any JSX tag. Its purpose is to allow adding data with markup to be rendered unescaped using innerHTML. You put the property on the element into which you wish to insert the data. Then pass the data to the property inside curly braces like any other dynamic property. You can learn more about how to use it here

Security Warning

Using innerHTML is very dangerous as it can lead to injection of malicious code in your site by hackers. Only use this if you are absolutely sure your data source is 100% secure. It is best to avoid using innerHTML and depend on the default rendering methods because these automatically encode the data, preventing script injection.

DataStore

This lets you create a dataStore that you can use with components. DataStore has only one public method: setState. When you change a dataStore's state with setState, the dataStore dispatches an event dataStoreStateChanged. When this happens, it there is a component linked to that dataStore, it will react to it by re-render itself with the current state of the dataStore. This combination gives you state management for stateless components. The component is reactive in that whenever the state of the dataStore changes, the component updates.

Creating a dataStore

To create a dataStore, pass in the data you want to use for state when you instantiate a new instance of DataStore. When you pass the data in, you need to wrap it in an object literal with a property of state. Pay attention to how we do this below:

import { h } from 'composi'
import { DataStore, DataStoreComponent } from 'composi/data-store'

// Create new dataStore:
const dataStore = new DataStore({
  state: {
    fruits: ['apples', 'oranges', 'bananas'],
    vegetables: ['potatoes', 'onions', 'carrots']
  }
})

To access the data in the dataStore we need to do so like this:

dataStore.state.fruits
// or:
dataStore.state.vegetables

setState

When you want to change the state of a dataStore, you use the setState method. This takes a callback which gets passed the previous state of the dataStore. This is usually refered to as prevState. This is the state property of the dataStore, so you can access the values of the state directly from it. No need to query from a state property. Notice how we add a new fruit and vegetable to our previous dataStore:

dataStore.setState(prevState => {
  prevState.fruits.push('strawberries')
  prevState.vegetables.push('tomatoes')
  // Don't forget to return prevState or state won't be updated:
  return prevState
})

Using setState on a dataStore cause it to dispatch and event dataStoreStateChanged along with its updated state. If you have a dataStore component, it is listening for that event. When it sees the event has occurred, it re-renders itself with the data that dataStore passed. To see how this works, read the next section about DataStoreComponent:

DataStoreComponent

DataStoreComponent is a custom version of Composi's Component class. The difference is that DataStoreComponent expects to receive a dataStore when it is initialized. During its initialization, it takes the provided dataStore and setting up a watcher that will re-render the component when the dataStore's state changes.

You create a new dataStore component by extending DataStoreComponent, just like you would with Component. But you will never give it state. Instead you will provide a dataStore as the value of the component's dataStore property during initialization. Otherwise, you will use the render function, lifecycle hooks and whatever methods you need, just like you would with the Component class.

Here is an example of a dataStore with a very simple dataStore component. When we update the dataStore, the component will also update.

import { h } from 'composi'
import { DataStore, DataStoreComponent } from 'composi/data-store'

// Create a dataStore:
const dataStoreTitle = new DataStore({
  state: {
    title: 'The Default Title'
  }
})

// Create a dataStore component:
class Title extends DatatStoreComponent {
  render(data) {
    return (
      

{data.title}

) } } // Create an instance of Title: const new title({ container: 'header', dataStore: dataStoreTitle }) // Mount the component instance by passing in the dataStore state: title.update(dataStore.state) // Some time later, change the state of the dataStore: dataStore.setState(prevState => { prevState.title = 'This is the new title' // Don't forget to return prevState: return prevState }) // The above will cause the title component to update with 'This is the new title'.

For more details on using DataStore and DataStoreComponent, check out the tutorial.

Observer

Composi has an Observer class, which it uses to enable communication between the dataStore and dataStore component. You can also use the Observer in your own code. You need to import it from Composi's data-store folder, just like you do for DataStore and DataStoreComponent.

Observer has only two methods: watch and dispatch. watch takes two arguments, an event and a callback. The callback receives as its argument any data passed with the event when it occurs. dispatch takes two arguments, an event and optionally any data you want to pass to the watcher. Data is optional in that you can create an observer that just responds to an event.

Here's an example of a simple observer:

import { Observer } from 'composi/data-store'

const observer = new Observer()
// Set up a watcher on the observer:
observer.watch('something-happened', data => {
  console.log('The event "something-happened" just happened')
  console.log(`The data is: ${data}`)
})
// Dispatch the event with some data:
observer.dispatch('something-happened', 'This is the data.')

The above is an overly simplist example. You could do much more complicated things with an observer. In fact, DataStore and DataStoreComponent are using it to link them together for reactive rendering.

uuid

Composi uses the uuid function to create uuids for DataStore. You can also use uuid inside your code. To do so, just import it from the data-store folder like DataStore, etc. uuid creates an RFC4122 version 4 compliant uuid, or string of 36 characters.

import { uuid } from 'composi/data-store'

// Create a new uuid:
const id = new uuid()