Component object

Each Riot.js component is created as lightweight object. The object that you export via export default will have the following properties:

  • Attributes
    • props - the props received as object
    • state - the current component state object
    • root - root DOM node
  • Create/Destroy
    • mount - initialize the component
    • unmount - destroy the component and remove it from the DOM
  • State handling methods
    • update - method to update the component state
    • shouldUpdate - method to pause the component rendering
  • Lifecycle callbacks
    • onBeforeMount - called before the component will be mounted
    • onMounted - called after the component has rendered
    • onBeforeUpdate - called before the component will be updated
    • onUpdated - called after the component has been updated
    • onBeforeUnmount - called before the component will be removed
    • onUnmounted - called once the component has been removed
  • Helpers
    • $ - method similar to document.querySelector
    • $$ - method similar to document.querySelectorAll

Component Interface

If you familiar with Typescript here you can read how a Riot.js component interface looks like:

  1. // This interface is only exposed and any Riot component will receive the following properties
  2. interface RiotCoreComponent<P = object, S = object> {
  3. // automatically generated on any component instance
  4. readonly props: P
  5. readonly root: HTMLElement
  6. readonly name?: string
  7. // TODO: add the @riotjs/dom-bindings types
  8. readonly slots: any[]
  9. mount(
  10. element: HTMLElement,
  11. initialState?: S,
  12. parentScope?: object
  13. ): RiotComponent<P, S>
  14. update(
  15. newState?: Partial<S>,
  16. parentScope?: object
  17. ): RiotComponent<P, S>
  18. unmount(keepRootElement?: boolean): RiotComponent<P, S>
  19. // Helpers
  20. $(selector: string): HTMLElement
  21. $$(selector: string): [HTMLElement]
  22. }
  23. // All the RiotComponent Public interface properties are optional
  24. interface RiotComponent extends RiotCoreComponent<P = object, S = object> {
  25. // optional on the component object
  26. state?: S
  27. // optional alias to map the children component names
  28. components?: {
  29. [key: string]: RiotComponentShell<P, S>
  30. }
  31. // state handling methods
  32. shouldUpdate?(newProps: P, currentProps: P): boolean
  33. // lifecycle methods
  34. onBeforeMount?(currentProps: P, currentState: S): void
  35. onMounted?(currentProps: P, currentState: S): void
  36. onBeforeUpdate?(currentProps: P, currentState: S): void
  37. onUpdated?(currentProps: P, currentState: S): void
  38. onBeforeUnmount?(currentProps: P, currentState: S): void
  39. onUnmounted?(currentProps: P, currentState: S): void
  40. [key: string]: any
  41. }

You can use any of the component properties in both the HTML and javascript code. For example:

  1. <my-component>
  2. <h3>{ props.title }</h3>
  3. <script>
  4. export default {
  5. onBeforeMount() {
  6. const {title} = this.props
  7. }
  8. }
  9. </script>
  10. </my-component>

You can freely set any property to the component scope and it will be available in the HTML expressions. For example:

  1. <my-component>
  2. <h3>{ title }</h3>
  3. <script>
  4. export default {
  5. onBeforeMount() {
  6. this.title = this.props.title
  7. }
  8. }
  9. </script>
  10. </my-component>

Note: if you have some globals, you can also use these references in both the HTML and javascript code:

  1. window.someGlobalVariable = 'Hello!'
  1. <my-component>
  2. <h3>{ window.someGlobalVariable }</h3>
  3. <script>
  4. export default {
  5. message: window.someGlobalVariable
  6. }
  7. </script>
  8. </my-component>

:warning: beware that the use of global variables in your components might compromise their server side rendering and it's highly not recommended.

Create/Destroy

component.mount

component.mount(element: HTMLElement, initialState?: object, parentScope?: object): RiotComponent;

Any component object will be mounted on a DOM node in order to rendered its template becoming interactive.

You will likely never call the component.mount method by yourself, you will use instead the riot.mount or riot.component instead.

component.unmount

component.mount(keepRoot?: boolean): RiotComponent

Detaches the custom component and its children from the page.If you want to unmount a tag without removing the root node you need to pass true to the unmount method

Unmount the tag and remove it template from the DOM:

  1. myComponent.unmount()

Unmount the component keeping the root node into the DOM:

  1. myComponent.unmount(true)

State handling

component.state

Any Riot.js component created has a state object property. The state object is meant to store all the mutable component properties. For example:

  1. <my-component>
  2. <button>{ state.message }</button>
  3. <script>
  4. export default {
  5. // initial component state
  6. state: {
  7. message: 'hi'
  8. }
  9. }
  10. </script>
  11. </my-component>

In this case the component is created with an initial state that can be modified internally via component.update.

You should avoid to store nested javascript objects into the state property because their references will be shared across multiple component and might generate side effects. To avoid undesired surprises you can create your components also using a factory function

  1. <my-component>
  2. <button>{ state.message }</button>
  3. <script>
  4. export default function MyComponent() {
  5. // remember to return an object
  6. return {
  7. // the initial state will be always fresh created avoiding surprises
  8. state: {
  9. nested: {
  10. properties: 'are ok now'
  11. },
  12. message: 'hi'
  13. }
  14. }
  15. }
  16. </script>
  17. </my-component>

Riot.js will automatically call the function anytime a new component will be mounted.

component.components

If you want to avoid registering global Riot.js components you can map your children components directly on your component object. For example:

  1. <my-component>
  2. <!-- this component is only available in `<my-component>` -->
  3. <my-child/>
  4. <!-- this component is named differently and was aliased -->
  5. <aliased-name/>
  6. <!-- this component was already registered via riot.register -->
  7. <global-component/>
  8. <script>
  9. import MyChild from './my-child.riot'
  10. import User from './user.riot'
  11. export default {
  12. components: {
  13. MyChild,
  14. 'aliased-name': User
  15. }
  16. }
  17. </script>
  18. </my-component>

The components property should be a static attribute of your component export. If you export functions your components should be declared as follows:

  1. <my-component>
  2. <button>{ state.message }</button>
  3. <script>
  4. import MyChild from './my-child.riot'
  5. import User from './user.riot'
  6. // static property
  7. MyComponent.components = {
  8. MyChild,
  9. 'aliased-name': User
  10. }
  11. export default function MyComponent() {
  12. // remember to return an object
  13. return {
  14. // the initial state will be always fresh created avoiding surprises
  15. state: {
  16. nested: {
  17. properties: 'are ok now'
  18. },
  19. message: 'hi'
  20. }
  21. }
  22. }
  23. </script>
  24. </my-component>

For this example we assume that you are bundling your application via webpack, rollup, parcel or browserify

component.update

component.update(newState?:object, parentScope?: object): RiotComponent;

Updates the component state object re-rendering all its expressions. This method can be usually called every time an event handler is dispatched when the user interacts with the application.

  1. <my-component>
  2. <button onclick={ onClick }>{ state.message }</button>
  3. <script>
  4. export default {
  5. state: {
  6. message: 'hi'
  7. },
  8. onClick(e) {
  9. this.update({
  10. message: 'goodbye'
  11. })
  12. }
  13. }
  14. </script>
  15. </my-component>

You can call this method also manually whenever you need to update your components UI. This typically happens after some non-UI related event: after setTimeout, AJAX call or on some server event. For example:

  1. <my-component>
  2. <input name="username" onblur={ validate }>
  3. <span class="tooltip" if={ state.error }>{ state.error }</span>
  4. <script>
  5. export default {
  6. async validate() {
  7. try {
  8. const {username} = this.props
  9. const response = await fetch(`/validate/username/${username}`)
  10. const json = response.json()
  11. // do something with the response
  12. } catch (error) {
  13. this.update({
  14. error: error.message
  15. })
  16. }
  17. }
  18. }
  19. </script>
  20. </my-component>

On above example the error message is displayed on the UI after the update() method has been called.

If you want to have more control over your tags DOM updates you can set rely on the shouldUpdate function return.Riot.js will update your component only if that function will return true

  1. <my-component>
  2. <button onclick={ onClick }>{ state.message }</button>
  3. <script>
  4. export default {
  5. state: {
  6. message: 'hi'
  7. },
  8. onClick(e) {
  9. this.update({
  10. message: 'goodbye'
  11. })
  12. },
  13. shouldUpdate(newProps, currentProps) {
  14. // do not update
  15. if (this.state.message === 'goodbye') return false
  16. // if this.state.message is different from 'goodbye' we could update the component
  17. return true
  18. }
  19. }
  20. </script>
  21. </my-component>

The shouldUpdate method will always receive 2 arguments: the first one contains the new component properties and the second argument the current ones.

  1. <my-component>
  2. <child-tag message={ state.message }></child-tag>
  3. <button onclick={ onClick }>Say goodbye</button>
  4. <script>
  5. export default {
  6. state: {
  7. message = 'hi'
  8. },
  9. onClick(e) {
  10. this.update({
  11. message: 'goodbye'
  12. })
  13. }
  14. }
  15. </script>
  16. </my-component>
  17. <child-tag>
  18. <p>{ props.message }</p>
  19. <script>
  20. export default {
  21. shouldUpdate(newProps, currentProps) {
  22. // update the DOM depending on the new properties received
  23. return newProps.message !== 'goodbye'
  24. }
  25. }
  26. </script>
  27. </child-tag>

Slots

The <slot> tag is a special Riot.js core feature that allows you to inject and compile the content of any custom component inside its template in runtime.For example using the following riot tag my-post

  1. <my-post>
  2. <h1>{ props.title }</h1>
  3. <p><slot/></p>
  4. </my-post>

anytime you will include the <my-post> tag in your app

  1. <my-post title="What a great title">
  2. My beautiful post is <b>just awesome</b>
  3. </my-post>

once mounted it will be rendered in this way:

  1. <my-post>
  2. <h1>What a great title</h1>
  3. <p>My beautiful post is <b>just awesome</b></p>
  4. </my-post>

The expressions in slot tags will not have access to the properties of the components in which they are injected

  1. <!-- This tag just inherits the yielded DOM -->
  2. <child-tag>
  3. <slot/>
  4. </child-tag>
  5. <my-component>
  6. <child-tag>
  7. <!-- here the child-tag internal properties are not available -->
  8. <p>{ message }</p>
  9. </child-tag>
  10. <script>
  11. export default {
  12. message: 'hi'
  13. }
  14. </script>
  15. </my-component>

Named Slots

The <slot> tag provides also a mechanism to inject html in specific sections of a component template

For example using the following riot tag my-other-post

  1. <my-other-post>
  2. <article>
  3. <h1>{ opts.title }</h1>
  4. <h2><slot name="summary"/></h2>
  5. <article>
  6. <slot name="content"/>
  7. </article>
  8. </article>
  9. </my-other-post>

anytime you will include the <my-other-post> tag in your app

  1. <my-other-post title="What a great title">
  2. <span slot="summary">
  3. My beautiful post is just awesome
  4. </span>
  5. <p slot="content">
  6. And the next paragraph describes just how awesome it is
  7. </p>
  8. </my-other-post>

once mounted it will be rendered in this way:

  1. <my-other-post>
  2. <article>
  3. <h1>What a great title</h1>
  4. <h2><span>My beautiful post is just awesome</span></h2>
  5. <article>
  6. <p>
  7. And the next paragraph describes just how awesome it is
  8. </p>
  9. </article>
  10. </article>
  11. </my-other-post>

Lifecycle

Each component object can rely on the following callbacks to handle its internal state:

  • onBeforeMount - called before the component will be mounted
  • onMounted - called after the component has rendered
  • onBeforeUpdate - called before the component will be updated
  • onUpdated - called after the component has been updated
  • onBeforeUnmount - called before the component will be removed
  • onUnmounted - called once the component has been removedFor example:
  1. <my-component>
  2. <p>{ state.message }</p>
  3. <script>
  4. export default {
  5. onBeforeMount() {
  6. this.state = {
  7. message: 'Hello there'
  8. }
  9. }
  10. }
  11. </script>
  12. </my-component>

All the lifecycle methods will receive 2 arguments props and state, they are aliases of the this.props and this.state component attributes.

  1. <my-component>
  2. <p>{ state.message }</p>
  3. <script>
  4. export default {
  5. state: {
  6. message: 'Hello there'
  7. },
  8. onBeforeMount(props, state) {
  9. console.assert(this.state === state) // ok!
  10. console.log(state.message) // Hello there
  11. }
  12. }
  13. </script>
  14. </my-component>

Helpers

Any Riot.js component provides two helpers to query DOM nodes contained in its rendered template.

  • component.$(selector: string): HTMLElement - returns a single node located in the component markup
  • component.$$(selector: string): [HTMLElemet] - returns all the DOM nodes matched by the selector containing the component markupYou can use the component helpers for doing simple DOM queries:
  1. <my-component>
  2. <ul>
  3. <li each={ item in items }>
  4. { item }
  5. </li>
  6. </ul>
  7. <script>
  8. export default {
  9. onMounted() {
  10. // queries
  11. const ul = this.$('ul')
  12. const lis = this.$$('li')
  13. // do something with the DOM nodes
  14. const lisWidths = lis.map(li => li.offsetWidth)
  15. const {top, left} = ul.getBoundingClientRect()
  16. }
  17. }
  18. </script>
  19. </my-component>

:warning:Beware that the $ and $$ helpers will perform a DOM query also through the DOM nodes generated by the children Riot.js components contained into your template.

Manual construction

Riot.js components are meant to be compiled to javascript via @riotjs/compiler. However you can build them manually with any rendering engine you like.

Component shell interface

The Riot.js compiler just creates a shell object that will be transformed internally by riot to create the component object. If want to build this shell object manually it’s worth to understand its interface first:

  1. interface RiotComponentShell<P = object, S = object> {
  2. readonly css?: string
  3. readonly exports?: () => RiotComponentExport<P, S>|object
  4. readonly name?: string
  5. template(): any
  6. }

The RiotComponentShell object consists of 4 properties:

  • css - the component css as string
  • exports - the component export default public API
  • name - the component name
  • template - the factory function to manage the component template

Template interface

The template function should return an interface compatible to the following one:

  1. interface RiotComponentTemplate {
  2. update(scope: object): RiotComponentTemplate;
  3. mount(element: HTMLElement, scope: object): RiotComponentTemplate;
  4. createDOM(element: HTMLElement): RiotComponentTemplate;
  5. unmount(scope: object): RiotComponentTemplate;
  6. clone(): RiotComponentTemplate;
  7. }

The RiotComponentTemplate is an object and it’s responsible to handle the component rendering:

  • update - method that will receive the component data and must be used to update the template
  • mount - method that must be used to connect the component template to a DOM node
  • createDOM - factory function might be needed to create the template DOM structure only once
  • unmount - method to clean up the component DOM
  • clone - method might be needed to clone the original template object in order to work with multiple instances

Examples

This example uses the @riotjs/dom-bindings (riot core template engine)

  1. import { template, expressionTypes } from '@riotjs/dom-bindings'
  2. riot.register('my-component', {
  3. css: ':host { color: red; }',
  4. name: 'my-component',
  5. exports: {
  6. onMounted() {
  7. console.log('I am mounted')
  8. }
  9. },
  10. template() {
  11. return template('<p><!----></p>', [{
  12. selector: 'p',
  13. expressions: [
  14. {
  15. type: expressionTypes.TEXT,
  16. childNodeIndex: 0,
  17. evaluate: scope => `Hello ${scope.greeting}`
  18. }
  19. ]
  20. }])
  21. }
  22. })

Read about the template engine API

You can also use any other kind of template engine if you like.This example uses lit-html as template engine

  1. import {html, render} from 'lit-html'
  2. riot.register('my-component', {
  3. css: ':host { color: red; }',
  4. name: 'my-component',
  5. exports: {
  6. onMounted() {
  7. console.log('I am mounted')
  8. }
  9. },
  10. template() {
  11. const template = ({name}) => html`<p>Hello ${name}!</p>`
  12. return {
  13. mount(element, scope) {
  14. this.el = element
  15. this.update(scope)
  16. },
  17. update(scope) {
  18. render(template(scope), this.el)
  19. },
  20. unmount() {
  21. this.el.parentNode.removeChild(this.el)
  22. },
  23. // the following methods are not necessary for lit-html
  24. createDOM(element) {},
  25. clone() {}
  26. }
  27. }
  28. })

Tags without template

You can also create “wrapper tags” without any template as follows:

  1. riot.register('my-component', {
  2. name: 'my-component',
  3. exports: {
  4. onMounted() {
  5. console.log('I am mounted')
  6. }
  7. }
  8. })

In this case anytime you will mount a tag named my-component riot will leave the component markup as it is without parsing it:

  1. <html>
  2. <body>
  3. <my-component>
  4. <p>I want to say Hello</p>
  5. </my-component>
  6. </body>
  7. </html>

This technique might be used to enhance serverside rendered templates.