Simple p5.js wrapper for React.
npm add @korkje/p5-react
import React from "react";
import P5React, { Sketch } from "@korkje/p5-react";
const sketch: Sketch = p => {
p.setup = () => p.createCanvas(100, 100);
p.draw = () => {
p.background(0);
p.ellipse(50, 50, 80, 80);
};
};
export default () => <P5React sketch={sketch} />;
For most use cases, you will find that this works just like normal p5.js in instance mode. However, there are some extra features and differences worth mentioning.
The parent element of the canvas is passed to the sketch as the second argument. This can be useful, e.g. if you want the canvas to fill the parent element.
const sketch: Sketch = (p, parent) => {
p.setup = () => p.createCanvas(
parent.clientWidth,
parent.clientHeight,
);
// ...
};
export default () => <P5React
sketch={sketch}
style={{ width: "100px", height: "100px" }}
/>;
The parent element is also set on the p5.js instance, if you prefer to use that.
const sketch: Sketch = p => {
p.setup = () => p.createCanvas(
p.parent.clientWidth,
p.parent.clientHeight,
);
// ...
};
When the P5React
component is unmounted, the p5.js instance is automatically removed. But you might also want to clean up other resources on unmount, e.g. timers set in the sketch. You can do this by returning a cleanup function.
const sketch: Sketch = p => {
const handle = setInterval(() => {
// ...
}, 1000);
return () => clearInterval(handle);
};
P5React
supports passing props into the sketch. For TypeScript users, props are defined as a type argument to the Sketch
type, and they are made available on the p5.js instance as p.props
.
const sketch: Sketch<{
count: number;
w: number;
h: number;
}> = p => {
p.setup = () => p.createCanvas(p.props.w, p.props.h);
p.draw = () => {
p.background(0);
p.text(p.props.count, 50, 50);
};
};
export default () => {
const [count, setCount] = useState(0);
return <>
<P5React sketch={sketch} props={{ count, w: 100, h: 100 }} />
<button onClick={() => setCount(ps => ps + 1)}>
Increment
</button>
</>;
};
Similar to React's useEffect
, you can use p.effect
to run code when props change (and initially). The first argument is a function that takes in the new props object and an initial
flag (which is true
on the first run), and the second argument is an optional array of dependencies (keys of the props object).
const sketch: Sketch<{
count: number;
w: number;
h: number;
}> = p => {
p.setup = () => p.createCanvas(p.props.w, p.props.h);
p.draw = () => {
p.background(0);
p.text(p.props.count, 50, 50);
};
p.effect(({ w, h }, initial) => {
if (!initial) {
p.resizeCanvas(w, h);
}
}, ["w", "h"]);
};
The second argument may also be a function that takes in the new props object and returns an array of dependencies, if you are so inclined.
const sketch: Sketch<{
arr: number[];
}> = p => {
p.setup = () => p.createCanvas(100, 100);
p.effect(({ arr }) => {
console.log(`Array length: ${arr.length}`);
}, ({ arr }) => [arr.length]);
};
Note that on subsequent runs, p.props
will only be updated after all effects have run. This means that in your effects, you can access both new and old props, if needed.
By default, props are updated when any of the prop values change according to shallow comparison, in fact Object.values
is used to create a dependency array for the internal useEffect
hook that updates props (and runs effects).
You may alter this behavior by supplying a deps
array to the P5React
component. This array will be passed to the internal useEffect
hook instead of the prop values.
This could be useful if you e.g. are creating and passing object props, and only want to update when a specific property of the object changes, though I would consider memoizing the object in that case.
export default () => {
const size = getSketchSize("my-sketch"); // { w: 100, h: 100 }
return (
<P5React
sketch={sketch}
props={{ size }}
deps={[size.w, size.h]}
/>
);
};
Using memoization, the deps array could be omitted.
export default () => {
const size = useMemo(() => getSketchSize("my-sketch"), []);
return <P5React sketch={sketch} props={{ size }} />;
};
The P5React
component isn't memoized, but it should be very inexpensive if used correctly. It contains two useEffect
hooks, one for creating the p5.js instance and one for updating props. The first is only run when the sketch
prop changes, and the second is only run when any of the props
(or deps
if supplied) change.
If you want to use p5.sound
, be aware that it expects p5
to be globally available before it is imported. You can achieve this by doing something like this:
import p5 from "p5";
(window as any).p5 = p5;
await import("p5/lib/addons/p5.sound");