Minimal React wrapper for q5.js
Build smooth, high-performance 2D games and visuals in React using q5.js (a blazing fast p5.js alternative).
This package gives you a clean, stable <Q5Canvas />
component with full control over drawing + real-time state.
- ✅ React + Vite-ready
- ✅ Works with React 18 / StrictMode
- ✅ Fullscreen support
- ✅ Easy overlay support
- ✅ Share real-time game state without re-rendering
- ✅ Type-safe state management with
useCreateCanvas
- ✅ Strongly typed drawing functions with React-like state updates
q5 is an awesome library for building web games and experiences, but oftentimes you want something more complex than a website with just a canvas on it - perhaps some menus or actions before you get to the actual canvas. While q5 is compatible with React thanks to instance mode, setting it up in modern React requires a few "gotchas" that might be tricky for a newcomer. For example, simply initiating a new q5 instance on initial render will result in two canvases being drawn at the same time, with only one of them being active (this is because of React's Strict Mode which is usually enabled by default - disabling it is a solution but not a valid one). This library aims to smoothen this experience by taking care of all the annoyances and letting you focus on just the fun parts.
npm install q5-react q5
q5
is a peer dependency — make sure it's installed too
import Q5Canvas, { useCreateCanvas } from "q5-react";
function MyComponent() {
const myCanvas = useCreateCanvas(
{}, // initial state (empty in this case)
(p) => {
p.background(0);
p.fill(255);
p.ellipse(p.mouseX, p.mouseY, 20, 20);
}
);
return <Q5Canvas canvas={myCanvas} size={500} />;
}
import Q5Canvas, { useCreateCanvas } from "q5-react";
// define your state type
type MyCountStateType = {
count: number;
position: { x: number; y: number };
};
function App() {
const myCanvas = useCreateCanvas<MyCountStateType>(
// define initial state
{
count: 0,
position: { x: 100, y: 100 },
},
// define your draw function
(p, state, { pressedKeys, pressedMouseButtons }) => {
p.background(0);
p.fill(255);
for (let i = 0; i < state.get().count; i++) {
p.ellipse(i * 10 + 5, 50, 10, 10);
}
const text = `Count is: ${state.get().count}`;
p.textSize(24);
p.text(text, p.width / 2 - p.textWidth(text) / 2, p.height / 2);
p.ellipse(state.get().position.x, state.get().position.y, 20, 20);
// handle keyboard input
if (pressedKeys.has("ArrowLeft")) {
state.set((prev) => ({
position: {
x: prev.position.x - 3,
y: prev.position.y,
},
}));
}
if (pressedKeys.has("ArrowUp")) {
state.set((prev) => ({
position: {
x: prev.position.x,
y: prev.position.y - 3,
},
}));
}
if (pressedKeys.has("ArrowRight")) {
state.set((prev) => ({
position: {
x: prev.position.x + 3,
y: prev.position.y,
},
}));
}
if (pressedKeys.has("ArrowDown")) {
state.set((prev) => ({
position: {
x: prev.position.x,
y: prev.position.y + 3,
},
}));
}
// handle mouse input
if (pressedMouseButtons.has(p.LEFT)) {
state.set((prev) => ({
position: {
x: prev.position.x - 3,
y: prev.position.y,
},
}));
} else if (pressedMouseButtons.has(p.RIGHT)) {
state.set((prev) => ({
position: {
x: prev.position.x + 3,
y: prev.position.y,
},
}));
}
}
);
return (
<div>
<button
onClick={() =>
myCanvas.state.set((prev) => ({ count: prev.count + 1 }))
}
>
Increase count
</button>
<Q5Canvas canvas={myCanvas} size={500}>
{({ toggleFullscreen }) => (
<button onClick={toggleFullscreen}>Fullscreen</button>
)}
</Q5Canvas>
</div>
);
}
export default App;
Prop | Type | Description | Default |
---|---|---|---|
canvas |
CreateCanvasProps<T> from useCreateCanvas<T> |
Canvas object with draw function and state | Required |
size |
"fullscreen" or number or [number, number] |
Canvas size. "fullscreen" will take up the entire page | 500 |
children |
React.ReactNode or (({ toggleFullscreen }) => React.ReactNode) |
HTML element to overlay on top of the canvas. Useful for buttons, menus, etc. | undefined |
- Use
useCreateCanvas<T>
to create a canvas with type-safe, persistent state that survives hot reloads - Access state with
canvas.state.get()
and update it withcanvas.state.set()
- Update state with React-like updater functions:
state.set(prev => ({ ...prev, count: prev.count + 1 }))
- Subscribe to state changes with
canvas.state.subscribe(callback)
- The draw function provides access to
pressedKeys
andpressedMouseButtons
for input handling - Resize support is automatic when using
size="fullscreen"
const canvas = useCreateCanvas<StateType>(
initialState: T | (() => T),
draw: (p: q5, state: CanvasState<T>, extras: DrawExtras) => void
): CreateCanvasProps<T>
Creates a canvas object with a persistent state container and draw function. Returns an object with:
draw
- Your drawing functionstate
- A state object with the following methods:get(): T
- Returns the current stateset(partialOrUpdater: Partial<T> | ((prevState: T) => Partial<T>))
- Updates the state using either a partial object or an updater functionsubscribe(callback: (state: T) => void): () => boolean
- Subscribe to state changes, returns an unsubscribe function
The draw function passed to useCreateCanvas
receives:
p: q5
- The q5 instance for drawingstate: CanvasState<T>
- Your typed state with get/set/subscribe methodsextras: DrawExtras
- Extra utilities including:pressedKeys: Set<string>
- Set of currently pressed keyspressedMouseButtons: Set<number>
- Set of currently pressed mouse buttons
If you are getting this error, create a q5.d.ts
file inside your src
folder with the following content:
declare module "q5" {
import q5 = require("q5");
export = q5;
}
This should take care of the issue.
@hxtnv - creator of q5-react
@quinton-ashley - original creator of q5.js