React
@niivue/nvreact wraps NiiVue for React 19. It has two entry points: a
standalone NvViewer component for one viewer, and a scene controller with
hooks for several viewers that share a layout. Both create ordinary NiiVue
instances, so everything in the other guides still applies once you have
the instance.
Install
The bindings for this version of NiiVue are published under the next tag,
alongside the matching NiiVue release:
npm install @niivue/nvreact@next @niivue/niivue@next react react-dom
@niivue/niivue is a peer dependency, so install it yourself.
One viewer
NvViewer creates a canvas inside a div, attaches a viewer to it, and
resizes it with the container. Pass the volumes you want shown:
import type { NiiVueOptions } from '@niivue/nvreact'
import { NvViewer, SLICE_TYPE } from '@niivue/nvreact'
const options: Partial<NiiVueOptions> = { backgroundColor: [0, 0, 0, 1] }
export function Brain({ colormap }: { colormap: string }) {
return (
<NvViewer
volumes={[{ url: '/volumes/mni152.nii.gz', colormap }]}
options={options}
sliceType={SLICE_TYPE.MULTIPLANAR}
style={{ height: 500 }}
onLocationChange={(data) => console.log(data)}
onImageLoaded={(volume) => console.log('loaded', volume.name)}
onError={(err) => console.error(err)}
/>
)
}
The volumes prop is declarative. On each change the component compares
the list by URL: it adds new volumes, removes ones that are gone, and calls
setVolume when colormap, calMin, calMax or opacity changes. Other
volume options are read only when a volume is first added.
options holds NiiVue options, merged over the package's
defaultViewerOptions. It is compared by value, so an inline
options={{ ... }} is fine. Options with a NiiVue setter update the live
viewer (a removed one reverts to its default). Construction-only options, such
as backend, isAntiAlias, bounds, matcaps or logLevel, rebuild the
viewer and reload its volumes. options.sliceType is only an initial value:
the sliceType prop always wins.
Several viewers
For more than one viewer, create a scene with useScene and render it with
NvScene. useScene returns the NvSceneController and a snapshot of its
state that re-renders your component when it changes:
import { NvScene, useScene } from '@niivue/nvreact'
export function Viewers() {
const { scene, snapshot } = useScene()
return (
<>
<button
disabled={!scene.canAddViewer()}
onClick={() => scene.addViewer()}
>
Add viewer
</button>
<button onClick={() => scene.setLayout('2x2')}>2 x 2</button>
<p>{snapshot.viewerCount} viewers</p>
<NvScene scene={scene} style={{ height: 600 }} />
</>
)
}
The snapshot has currentLayout, viewerCount, slots, isBroadcasting,
isLoading and viewerStates (each with an id, a loading count and
errors).
The controller manages the viewers:
addViewer(options?),removeViewer(index),canAddViewer()andclearViewers().setLayout(name)picks a grid fromdefaultLayouts:'1x1','1x2','2x1','1x3','3x1','2x2'and'3x3'.canAddViewer()is false once the layout is full.setViewerSliceLayout(index, layout)gives one viewer a custom tile arrangement, for exampledefaultSliceLayouts['axial-hero'].layout. Passnullto reset it.setBroadcasting(true)links every viewer that has an image loaded, so 2D and 3D view changes in one follow in the others. It uses NiiVue'sbroadcastTo(see Linking viewers). A second argument such as{ '2d': true, '3d': false }limits what is shared.loadVolume(index, options),loadVolumes(index, list)andremoveVolume(index, url)manage each viewer's images.
Reaching NiiVue
useNiivue(scene, index) returns the NiiVue instance for a viewer, or
undefined until that viewer exists. Use the regular property API on it:
const nv = useNiivue(scene, 0)
useEffect(() => {
if (nv) nv.isColorbarVisible = true
}, [nv])
useSceneEvent(scene, event, callback) subscribes to controller events and
unsubscribes on unmount. The callback can change between renders without
resubscribing:
useSceneEvent(scene, 'locationChange', (viewerIndex, data) => {
console.log(viewerIndex, data)
})
Events include viewerCreated, viewerRemoved, locationChange,
imageLoaded, volumeAdded, volumeRemoved and error. Outside React,
scene.on(event, handler) returns an unsubscribe function, and
scene.off(event, handler) removes a handler.
Sharing the scene
NvSceneProvider puts a scene in React context, and useSceneContext()
reads it in any child, so toolbars do not need the scene passed down:
import { NvScene, NvSceneProvider, useScene, useSceneContext } from '@niivue/nvreact'
function Toolbar() {
const scene = useSceneContext()
return <button onClick={() => scene.addViewer()}>Add</button>
}
export function App() {
const { scene } = useScene()
return (
<NvSceneProvider scene={scene}>
<Toolbar />
<NvScene scene={scene} style={{ height: 600 }} />
</NvSceneProvider>
)
}
The package entry point, src/lib.ts, lists every export.