Skip to content

Adapter details

The rest of the React adapter, after Getting Started: what <Riffle> passes through, the riffleRef handle, how option changes reach the engine, refs, and server components.

If you also import from the core directly, for example fan() to tune the layout, no extra install is needed: both entries ship in the same package.

Any prop that is not one of <Riffle>’s own (cards, children, getKey, onChange, riffleRef, cardClassName) and not a Riffle option goes to the root element: id, aria-label, data-* and so on, with style and className merged into what the adapter sets.

ref on <Riffle> reaches the root element, under React 18 and React 19 alike: the component is wrapped in forwardRef and composes your ref with its own.

riffleRef receives a RiffleInstance: next, prev, goTo, on, instance (the live engine, or null while unmounted), activeIndex and state (the current snapshot). Reading activeIndex or state from the ref does not re-render anything: use onChange for that, or useRiffle with useRiffleState below.

useRiffle(options) creates one engine per component instance and returns getRootProps and getCardProps, a pair of prop getters that wire the engine to whatever markup you render, plus the imperative handle (next, prev, goTo, subscribe, getSnapshot, on, instance). Getting Started shows a stack built this way.

Options are compared with the previous render’s, and only what changed reaches the engine’s update(). spring and rotation compare shallowly, so an inline object literal with the same values is not a change. Every other option, layout and getLabel included, compares by identity: pass a stable reference (module scope, useMemo, or a useState initializer) rather than a fresh object or function on every render, or every render calls update(). Only two changes rebuild the engine instead, keeping its position: a new axis, and cardWidth or cardHeight switching between a number and 'auto'.

startIndex is read once, when the engine is first built. Changing it later does nothing: call goTo to move.

getCardProps(index, props) wants a stable ref. If the ref you pass changes identity on every render, React detaches and reattaches it, which unregisters and re-registers that card with the engine every render. Pass a ref created once instead.

The root ref from getRootProps is safe to pass inline. Detaching it does not tear the engine down at once: teardown waits one microtask, and a re-attach to the same element before then cancels it. An inline ref callback, recreated every render, produces exactly that detach and reattach within one commit, so it changes nothing. The same holds for React’s StrictMode, which attaches, detaches and reattaches every ref at mount.

useRiffleState subscribes to one slice of the snapshot instead of the whole thing, so a component reading only activeIndex does not re-render when isDragging flips.

The package build begins with 'use client', so frameworks with React Server Components treat it as client code. Render <Riffle> from a client component of your own: its children is a function, and a server component cannot pass one across.