Skip to content
Genom
Platforms

React

A component for the common case, hooks for everything it does not cover.

The component

DocumentViewer renders the container, opens the file and layers the loading, error and empty states over it. The container is never unmounted between states: recreating it would lose the scroll position and force the view to be rebuilt.

Which formats it can open is a prop. That is what keeps this component the same size whether an application shows one format or six, and what lets the parsers arrive when a reader opens one.

Pane.tsx
import { DocumentViewer } from '@genomdev/react';
import { docx, xlsx, pdf } from '@genomdev/genom/lazy';

<DocumentViewer
  file={file}
  formats={[docx, xlsx, pdf]}
  fit="width"
  zoom={1}
  options={{ xlsx: { formulaBar: false } }}
  slots={{
    loading: (progress) => <Spinner value={progress} />,
    error: (error) => <Failed message={error.message} />,
    empty: <DropHint />,
  }}
  onPageChange={({ index, total }) => setPage(index + 1)}
  onStateChange={(state) => setStatus(state.status)}
/>;

Props

PropTypeNotes
fileFile | Blob | Uint8Array | string | GenomDocumentA URL is fetched; an already-open document is shown without parsing it again.
formatsFormatEntry[]Eager modules, lazy descriptors, or both. Nothing is registered globally.
fit'width' | 'page' | 'none'How a page is fitted into the container.
zoomnumber1 means 100%.
initialPagenumberZero-based page, sheet or slide.
optionsRecord<string, object>Renderer settings addressed by format id: { docx: …, xlsx: … }.
decorateRecord<string, Decorator>Change elements the renderer produced — see Extending.
pluginsViewerPlugin[]Extensions that get the viewer itself.
slots{ loading, error, empty }A node, or a function of the progress or the error.
onPageChange onDocumentOpen onError onStateChangecallbacksThin wrappers over the event bus; anything else is viewer.on(…).

The hooks

useViewer binds a viewer to a component’s lifecycle and returns the container ref to attach. State is read through useSyncExternalStore, so the viewer stays the single source of truth and nothing tears under concurrent rendering.

Custom.tsx
import { useViewer, useViewerEvent } from '@genomdev/react';
import { all } from '@genomdev/genom/lazy';

function Custom({ file }: { file: File }) {
  const { containerRef, state, viewer, open, close } = useViewer({
    formats: all,
    fit: 'width',
  });

  useViewerEvent(viewer, 'sheet:select', ({ range }) => setSelection(range));

  useEffect(() => {
    void open(file);
  }, [file, open]);

  return (
    <div>
      <Toolbar status={state.status} onClose={close} />
      <div ref={containerRef} style={{ height: '80vh', overflow: 'auto' }} />
    </div>
  );
}
useGenom
The old name of useViewer still works and is an alias, not a second implementation.

Reading what is on screen

The viewer can produce the content of the document it is showing, without parsing the file a second time — the format module it loaded also knows how to read it out.

Content
const content = await viewer.content();

content.toMarkdown();
content.chunks({ maxTokens: 512 });

const search = await viewer.search();
search.find('revenue');