Skip to main content
Technical preview

Redraw is currently in technical preview, available to wcandillon.dev subscribers. API is unstable.

React Native

react-native-redraw mirrors the react-redraw API on top of react-native-webgpu: the same <RedrawCanvas>, <RedrawProvider>, useRedraw(), and useSurface(). It requires react-native-webgpu as a peer dependency.

As on the web, every canvas and hook needs a <RedrawProvider> above it (or an explicit instance via the redraw prop) and throws a clear error otherwise. Wrap your app, or the part of it that draws, once:

// App.tsx
import { StyleSheet } from "react-native";
import { RedrawCanvas, RedrawProvider } from "react-native-redraw";

import { render, animate } from "./hello-world";

export function App() {
return (
<RedrawProvider>
<Hello />
</RedrawProvider>
);
}

function Hello() {
return (
<RedrawCanvas style={styles.canvas} render={render} animate={animate} />
);
}

const styles = StyleSheet.create({
canvas: {
flex: 1,
},
});

No matter how many canvases live under the provider, the app holds a single GPU device and shares one set of pipeline caches. The provider accepts every RedrawInit() option through its options prop, for example options={{ timestampQuery: true }} or an existing device with options={{ device }} (which stays yours).

Nesting

As on the web, nesting <RedrawProvider> is idempotent. To create an isolated instance with its own device, use <LocalRedrawProvider>, which always owns its instance even when nested.

RedrawCanvas

render runs once to build the scene (and again when the canvas resizes); animate runs every frame and mutates the nodes returned by render. For static scenes or immediate-mode rendering, use onDraw (with loop for an animation loop), exactly like on the web.

Loading and error states

The provider initializes asynchronously. By default children render immediately and canvases start drawing once the instance is ready. To show loading and unsupported states instead, pass fallback and errorFallback:

<RedrawProvider
fallback={<ActivityIndicator />}
errorFallback={(error) => <Text>WebGPU is not available</Text>}
>
<RedrawCanvas render={render} animate={animate} />
</RedrawProvider>

If the device is ever lost, the provider notifies onError, shows fallback again, and re-initializes with a fresh device; canvases resume automatically.

useRedraw

useRedraw() hands you the Redraw instance directly for imperative work on the same device and backend, e.g. offscreen rendering or loading textures. It suspends while the provider initializes, so it composes with your own <Suspense> and error boundaries.

useSurface

When you want to drive rendering yourself but stay in React, useSurface works exactly like on the web: it manages the canvas and a surface whose flush(canvas) presents the frame (internally through an offscreen surface and a blit pass):

import { Canvas as WGPUCanvas } from "react-native-webgpu";
import { useSurface } from "react-native-redraw";

function MyComponent() {
const { ref, surface } = useSurface();

useEffect(() => {
if (!surface) return;
const canvas = new Canvas();
// ... draw to canvas
surface.flush(canvas);
}, [surface]);

return <WGPUCanvas ref={ref} style={{ flex: 1 }} />;
}

Bring your own instance

Create an instance yourself with RedrawInit() and pass it via the redraw prop (or the hook's redraw option), which overrides any surrounding provider. Ownership is simple: whoever creates the instance owns its device. A canvas only ever destroys its own resources; the provider destroys the device it created on unmount; an instance you pass in stays yours.

Lifecycle callbacks

  • On the provider, onError fires when initialization fails or when the device is lost (a loss triggers automatic re-initialization).
  • On a canvas, onError fires when the device is lost or when rendering a frame throws; the animation loop stops first.
  • On a canvas, onReady receives the Redraw instance once it is ready, before the first frame. It is safe under React StrictMode.
  • On a canvas, onGPUTime receives the GPU time of the most recently completed shading pass in nanoseconds after each frame. It requires options={{ timestampQuery: true }} on the provider and a device that supports the timestamp-query feature (note: the iOS Simulator does not); when timestamps are unavailable, a one-time warning is logged instead.

Without an onError handler, errors are logged to the console.