Guide · React

Zumly with React

A view can be a React component. Zumly hands you an empty element; you mount a root in it and unmount it when the view goes away.

Live example · ReactOpen ↗

Each planet and the list are React components in their own views. Like a few times, open a planet, then come back: the count is still there.

The pattern

Make the view a function. It receives a context; mount a root in target.

import Zumly from 'zumly'
import 'zumly/style.css'
import { createRoot } from 'react-dom/client'
import Detail from './Detail'

const app = new Zumly({
  mount: '.canvas',
  initialView: 'home',
  views: {
    home: '<div class="z-view"><div class="zoom-me" data-to="detail" data-id="42">Open</div></div>',
    detail: ({ target, props, onCleanup }) => {
      const root = createRoot(target)
      root.render(<Detail id={props.id} />)
      onCleanup(() => root.unmount())
    }
  }
})
app.init()

A list that opens a detail

Zumly is created once, in the component that owns the canvas, and destroyed with it. The list renders the triggers; any element with zoom-me and data-to works, whoever rendered it.

App.jsx

import { useEffect, useRef } from 'react'
import { createRoot } from 'react-dom/client'
import Zumly from 'zumly'
import 'zumly/style.css'
import List from './List'
import Detail from './Detail'

// Each view gets its own React root
const mountView = (render) => ({ target, props, onCleanup }) => {
  const root = createRoot(target)
  root.render(render(props))
  onCleanup(() => root.unmount())
}

export default function App () {
  const canvas = useRef(null)

  useEffect(() => {
    const app = new Zumly({
      mount: canvas.current,
      initialView: 'list',
      views: {
        list: mountView(() => <List />),
        detail: mountView(props => <Detail id={props.id} />)
      }
    })
    app.init()
    return () => app.destroy()   // removes its views too
  }, [])

  // The canvas fills its nearest positioned ancestor: give that a height
  return <div style={{ position: 'relative', height: '100vh' }}>
    <div ref={canvas} className="zumly-canvas" />
  </div>
}

List.jsx

const items = [
  { id: '1', name: 'Engine' },
  { id: '2', name: 'Battery' },
  { id: '3', name: 'Brakes' }
]

export default function List () {
  return <div className="list">
    <h1>Subsystems</h1>
    {items.map(item =>
      <div key={item.id} className="zoom-me" data-to="detail" data-id={item.id}>{item.name}</div>
    )}
  </div>
}

When a view comes back

Sharing services and state

Redux, Zustand or a React context: pass it once in componentContext and every view receives it as context.

const app = new Zumly({
  ...,
  componentContext: { store },
  views: {
    detail: ({ target, props, context, onCleanup }) => {
      const root = createRoot(target)
      root.render(<Provider store={context.store}><Detail id={props.id} /></Provider>)
      onCleanup(() => root.unmount())
    }
  }
})

Things to know

import { createRoot } from 'react-dom/client'
import type { ViewFunction } from 'zumly'

const detail: ViewFunction = ({ target, props, onCleanup }) => {
  const root = createRoot(target)
  root.render(<Detail id={String(props.id)} />)
  onCleanup(() => root.unmount())
}

Next

Where views come from, in step 4. The same pattern for Vue · Svelte · Angular, and for radial layouts with Orbit.

Open this example ↗ Learn in six steps