Guides

React bindings

KymoscopeProvider, useEngine, useEngineState and the <Chart> component from @kymoscope/sdk/react.

Provide the engine

React
import { useEffect, useState } from 'react'
import { createEngine, type KymoscopeEngine } from '@kymoscope/sdk'
import { KymoscopeProvider } from '@kymoscope/sdk/react'
import '@kymoscope/sdk/styles/charts.css'

export function AnalysisRoot() {
  const [engine, setEngine] = useState<KymoscopeEngine | null>(null)
  useEffect(() => {
    const e = createEngine({ licenseKey: import.meta.env.VITE_KYMOSCOPE_LICENSE_KEY })
    setEngine(e)
    return () => e.dispose()
  }, [])
  return engine ? <KymoscopeProvider engine={engine}><AnalysisUi /></KymoscopeProvider> : null
}

useEngine() returns the engine anywhere below the provider, for calling operations.

Read state

useEngineState(selector, isEqual?) re-renders only when the selected value changes (by Object.is, or your isEqual). Select narrowly.

React
function ImportButton() {
  const engine = useEngine()
  const busy = useEngineState((s) => s.importing > 0)
  return (
    <label className="btn">
      {busy ? 'Importing…' : 'Import'}
      <input type="file" multiple hidden onChange={(e) => void engine.datasets.importFiles([...e.target.files!])} />
    </label>
  )
}

const names = useEngineState(
  (s) => s.datasets.map((d) => d.name),
  (a, b) => a.length === b.length && a.every((x, i) => x === b[i]),
)

Render charts

React
function ChartCard({ chart }: { chart: ChartModel }) {
  const [status, setStatus] = useState<ChartStatus | null>(null)
  const view = useRef<ChartView | null>(null)
  return (
    <section className="card">
      <header>
        {chart.title ?? chartLabel(chart.kind)}
        <button onClick={() => view.current?.exportPng().then(save)}>PNG</button>
      </header>
      <div style={{ position: 'relative', height: 300 }}>
        <Chart
          id={chart.id}
          style={{ height: '100%' }}
          options={{ theme }}
          onStatus={setStatus}
          onContextMenu={(e) => openMenu(chart.id, e)}
          viewRef={(v) => { view.current = v }}
        />
        {status === 'no-data' ? <Placeholder>Import a file to start</Placeholder> : null}
      </div>
    </section>
  )
}
  • <Chart> mounts on first render and destroys the view on unmount or when id changes.
  • options changes are applied with setOptions, without remounting.
  • Callback props can change on every render; the latest one is called.
  • viewRef receives the ChartView after mount and null on unmount.