@composi/core:

Render

Defining Markup

By default Composi uses JSX for defining the markup that it renders. This is declarative and makes it easy to reason about what the rendered markup will be.

Importing h and render

Before we can do anything, we need to import two essential functions from @composi/core: h and render:

import { h, render } from '@composi/core'

h gets used by Babel to transform the JSX tags into function calls to be create virtual nodes. These will be converted into real DOM nodes during render.

JSX

You can use JSX to define the markup that a functional component creates. Unlike React, you do not have to use special attributes or camel case with attributes. You use the normal HTML attributes: onclick='', class='', for='', etc. Because this is JSX, which is a variety of XML, you do need to terminate self-closing tags with a forward slash: <br/>, <input type='text'/>, <img src=''/>, etc.

Functional Components

To create a component that you can render, you need to start with a function. The name needs to be capitalized. Here is a simple Hello World function.

import { h, render } from '@composi/core'
function HelloWorld() {
  return (
    <h1>Hello, World!</h1>
  )
}

Notice that this function does one thing, it returns some JSX markup.

Rendering

We can now pass this function to the render function as a tag. The render function takes two arguments: the tag to render, and the container to render into. The tag should be a function written as a JSX tag. The container can be a DOM node reference, or a selector.

import { h, render } from '@composi/core'
function HelloWorld() {
  return (
    <h1>Hello, World!</h1>
  )
}
// Render the component to the DOM:
render(<HelloWorld />, document.body)

To udate a component, just pass it to render along with new values for its props. Let's update the above example:

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

Selector vs DOM Reference

In the above example we're using a string "header" as the container for the component. This is fine for occasional renders. However, if the component might be rendered many times in rapid succession, such as when animating a component, it would be better to use a DOM reference to that container. That's because when you provide a string selector for the container, each time the component is rendered, Composi has to query the DOM for that selector. Getting a reference to the container first and using that in the render function means that render can immediate start diffing and patching the DOM.

Passing Children to a Component

Sometimes you may want to be able to pass children to a component. You can enable any fuctional component to accept arbitrary children by passing it a second parameter after the props one. Then you can output whatever the children are by processing the children value inside of curly braces. Notice how we do that in this example. We have a FancyBorder that accepts any child component. This means we can pass anything we need to as a child. In this case, we pass in another component, DialogMessage, but it could be anything. This makes the FancyBorder component much more reusable.

See the Pen @composi/core - Passing Children to Component by Robert Biggs (@rbiggs) on CodePen.

Props

To make a component dynamic, we need to be able to pass some data into it. We do that through props. This is the main argument that any JSX tag receives. Whatever properties you give the tag will be available from this object. Let's redo our previous example to use props. Notice how we access the greeting value from props:

See the Pen @composi/core - Hello World-2 by Robert Biggs (@rbiggs) on CodePen.

Since we are now using props in our component, we can update the component at any time by re-rendering it with a new prop value. Let's say we want to update the value of our Hello World example after five seconds. To update a component, we just need to change the value of the prop when we re-render it:

See the Pen @composi/core - Hello World-3 by Robert Biggs (@rbiggs) on CodePen.

For more details about using props consult the documentation.

Conditional Rendering

Sometimes you need to render a component based on a certain criterion. There are two ways to handle conditional rendering: directly in the return statement, or before the return statement.

Return Statement Logic

The most common way to control is or how a component renders is to do so directly inside the return statement. To do this you use boolean checks with JavaScript logical operators (&&, ||, and !) or with a ternary operator. In fact, you can use these to return different markup based on your conditional checks. Because you are inside a return statement, you cannot use any conditional statements (if, if else, else).

Logical Operators

You can use logical operators to determine what to return in your statement. Notice how we use the AND operator (&&) and then the OR operator (||) to switch to the other option. However, when you want an if/else you need to use the OR operator for the else. The problem is that it is easy to forget to put the OR operator, in which case the second condition will never happen. This results in hard to debug situations.

// Two components to render:
function UserGreeting(props) {
  return <h1>Welcome back!</h1>
}

function GuestGreeting(props) {
  return <h1>Please sign in.</h1>
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn
  // With logical operators:
  return (
    isLoggedIn && <UserGreeting /> ||
    !isLoggedIn && <GuestGreeting />
  )
}

Using the AND operator is great when you just want to output based on one condition, basically the same as if. Notice how in this version, it is very clear what the intent is. However it is not branching logic.

// Only render greeting if the user is logged in.
// There is no alternative rendering here.
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn
  // With logical operators:
  return (
    isLoggedIn && <UserGreeting />
  )
}

Here's an example that shows how using logical operators can result in hard to read logic when the components are complex:

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

Ternary Operator

The ternary operator is more clear about what is going on, and more compact. This is a great choice for simple tags. However, if your tags have a lot of properties, then a ternary expression can be hard to read.

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn
  // With a tertiary conditional:
  return isLoggedIn ? <UserGreeting /> : <GuestGreeting />
}

Here's an example of using a ternary operator with complex components. Notice that its got the same basic structure as the previous live example using logical operators:

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

Logic Before Return

If you need conditional rendering without the drawbacks of logical and ternary operators, you can use standard conditional statements to determin what to return. Many developers prefer the more compact syntax of logical and ternary operators. However, with conditional statements there is no ambiguity about what is happening.

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn
  // With conditional statements:
  if (isLoggedIn) {
    return <UserGreeting />
  } else {
    return <GuestGreeting />
  }
}

Some people and linters don't like return statements in conditional statements because there is no explicit return. We can change that to fix the issue:

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn
  // With conditional statements:
  if (isLoggedIn) {
    return <UserGreeting />
  }
  // If the above logic failse,
  // return the guest greeting:
  return <GuestGreeting />
}

And here is this as a live example. This does result in more readable logic:

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

In the end, pick the approach that works best for you.

Not Rendering a Component

Sometimes you want to not render a component when a condition occurs. Using the above conditional state format we can solve this. Here's an example:

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

Notice how when showWarning is true, we return the component, otherwise we return null. We could also leave off the else statement for brevity. We could also use a simple logical operator for this:

function WarningBanner() {
  return (
    showWarning && (
      <div class="warning">
        Warning!
      </div>
    )
  )
}

In this case, the use of the logical operator is clear and concise. We could also use a ternary operator for this:

function WarningBanner() {
  return (
    showWarning ? (
      <div class="warning">
        Warning!
      </div>
    ) : null
  )
}

In this case, the ternary operator is not as concise as the logical operator. The ternary operator requires if/else logic, whereas the logical operator lets us handle an if condition.

Limits of Container

When @composi/core renders a component into a container, it caches the component's virtual node on the container. The next time you render that component, Composi grabs the cached virtual node from the container and uses that to diff and patch the DOM. This means that a container can have only one component. When you render a component into a container, if said container has static content, the component will be appended after the content.

If you need to output more than one component into the same container, wrap them in a parent component and render it in the component. You cannot use the Fragment tag for this, as it must be consumed by a tag that returns a singular element.

Debouncing Renders

It's possible that you may be using some type of event, such as cursor position, to trigger an update. This is very common for animation. In that case you would want to wrap the render function in requestAnimationFrame. This will result in skipping unnecessary attempts to render faster than the browser can handle. The format is like this:

requestAnimationFrame(() => {
  render(, 'body')
})

Check out the documentation about how to do this with @composi/datastore