In the course of my final project at the Flatiron School in software engineering, I really wanted to push my skills farther than what I was comfortable. We're given three weeks to accomplish a full-stack app in Rails and React, so I was looking for an idea that combines what I'd learned in the last 15 weeks as well as something I was aching to do. I'd toyed around with P5.js, a slick animation library for the browser (see prior blog post), and had seen different creators make some pretty cool audio visualizers.
Thankfully, through some other internet perusing, I'd settled on the idea to integrate p5.js and websockets to enable users to create audio visualizers collaboratively on the same canvases. You can read (and soon see the deployed project) on my personal site.
However, for the purposes of this post another blog post to come, I wanted to talk a little bit about how I was able to wrangle the disparate technologies to make my project possible.
Give Me All The Libraries
Coming from projects that were thought up, planned out, and built all within the course of a week, I'd learned to leaned on libraries to most of the heavy lifting for me. So my first instinct to make P5.js, and actioncable for websocketing, work was to look for some open source library that integrates the two with my React frontend. Although projects can be a blessing and make your job much easier, I learned very quickly that best option is to actually just learn the tech and not treat it like a black box.
In the case of P5, a couple people had written nice libraries to have your sketches play nice with React, such as P5-React and P5-React-Wrapper. These are definitely nice for a very surface level implementation, like maybe a nice background image with some animations or something else to just spruce up your website.
However, you aren't aware of what is happening under the hood for these pre-written components such that anything more complicated in P5 is liable to break the code or cause you some head scratching bugs. In my case, I needed to have the sketch respond to incoming data through the websocket and alter the sketch on the fly. Essentially, I had to mount a websocket cable inside the P5.js sketch. I'm assuming a base understanding of how sketching in P5 works, but the point I'm attempting to make here is that libraries are limited in their ability to suit the project you're working on. The problem I ran into was very case specific but it required full control over React, the websocket, and the P5 sketch. Essentially, I'd learned that when strapped for time, using a somebody's pre-written library, component, or whatever is probably a good idea. But if time permits or your problem requires that you need full control over your project, then you gotta go the long way and learn what you're doing.
Now For How to Do it
Once I'd discarded the components referenced above, then all you need to do is create a dependency for P5 as a node package, running this in the terminal:
npm i p5
while in the relevant directory for your React project.
This just lets us access the good stuff p5 has to offer. Next, P5 can either be created in a global or instance. What that difference amounts to is whether we want P5 in node or interacting with the DOM or window object. Seeing that React has its own virtual DOM and getting P5 to play nice, P5 in instance mode is our way to go.
Essentially, we're just creating a P5 object or instance that contains all the variables declared inside it, so as to not dilute our global namespace. This looks like:
const s = ( sketch ) => {
let x = 100;
let y = 100;
sketch.setup = () => {
sketch.createCanvas(200, 200);
};
sketch.draw = () => {
sketch.background(0);
sketch.fill(255);
sketch.rect(x,y,50,50);
};
};
let myp5 = new p5(s);
I've taken this from the processing/p5.js github. See this for more details on instance mode. But essentially we're encapsulating our sketch in a function that takes in a P5 sketch object that has access to all the P5 functions we like (such as setup, draw, background, etc. etc.). This just means that anything within P5 that we'd like to use must be accessed in the pattern of [name of sketch].[name of p5 function we want]. Anything outside of that, like variables or what have you will not change.
This means that there is no loss between P5 manipulating the DOM directly and P5 in instance mode, we just have to annoyingly declare the functions or P5 variables in the sketch object.
Now to React
You have some leeway in how you integrate your sketch into the component you want. I needed it to create a canvas that users could add, edit, or delete shapes on, so mine lived in a "canvas" component. Looking like this:
class App extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
Sketch = (p) => {
p.setup = () => {
...
}
p.draw = () => {
...
}
}
...
To distill the way I made this work with React, I've stripped away the things I'm doing in the sketch just to show exactly how the syntax and P5 functions work within the component. But the setup and draw functions work exactly as they do in regular P5. The naming is different than the instance object shown above, but the names do not matter whatsoever. "s" has become "Sketch" and the P5 sketch object has been named "p" so that I don't have to keep typing the same long word over and over again to reference P5 functions and variables.
Finally, to have our instance P5 attach to the DOM in react we have to give it some reference node to stand as its parent. Luckily, React has given us the ability to do this, as seen by
this.myRef = React.createRef()
in the constructor of our component. We will use this reference node to attach whatever we want for our P5 sketch. I declare the creation of the new P5 object and the reference node in the componentDidMount lifecycle method so that mounting kicks off the attachment of my P5 sketch. All together this looks like
class App extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
Sketch = (p) => {
p.setup = () => {
...
}
p.draw = () => {
...
}
}
componentDidMount() {
this.myP5 = new p5(this.Sketch, this.myRef.current)
}
render() {
return (
<div ref={this.myRef}>
</div>
)
}
}
And voila! We created a reference node in our constructor function called "this.myRef". We save our sketch in a function that takes in a P5 sketch object. We declare the creation of a new p5 object, giving it our function, and the reference node (the second argument is reserved for such a node instance). Then in our render, we have a div that is the reference node we declared in the constructor. Bada bing, bada boom, the P5 sketch is attached to the div in our render function, and P5 is totally integrated with React. No pre-made components. This allows for total control of our sketch within React. All it took was taking advantage of the instance mode of P5 and the creation of a reference node in React.
You can see exactly my implementation on my github, and I'll be sure to go into detail about how I used actioncable to mount a websocket connection within my sketch on a future blog post.
Top comments (20)
Thank you so much! I was initially using a package that ultimately made what I was trying to do more complicated (I also needed the ability for user input). I'd probably be tearing my hair out if I didn't stumble upon this.
Awesome! Stoked to help
Unfortunately this approach just keeps creating new instances of a p5 component on each "hot update" when using create-react-app or similar.
A solution can be found here lloydatkinson.net/posts/2022/how-t...
Basically, if you follow the example in the comments above all you need to do is:
This will remove the current p5 instance and creates a new one on the next render
yess.. did you find a solution for this? I'm trying to create a sorting visualizer using react and p5 and it's just not working..
Hello, thanks so much for this constructive document answer. I am new to react and i am developing a project with my friends. I have a doubt regarding the same topic. is there a way to do this as a functional component?
import React, { useEffect } from 'react';
import p5 from 'p5';
const App = () => {
const myRef = React.createRef();
const Sketch = (p) => {
}
useEffect(() => {
const myP5 = new p5(Sketch, myRef.current)
},[])
}
export default App;
Thanks for the response @anshul16. It will surely help someone in the community.
Hello Christian,
Thank you.
You can test your code here P5inReact
Regards
THANK YOU SO MUCH. I don't know why we can not import Javascript functions directly in to React components. I guess I am missing something fundamental. Your work is really helpful for my current project. P5.js is great. Cheers.
edit: I also love the way you've structured the projects. Great balance between readability and modularity.
can you be a bit more clear about why this is a better option than using the wrapper libraries, that people have put a lot of work into? I assume there's some complication that is created by that layer of abstraction but fear it would only be after spending a lot of time working on this that would become clear....?
Thanks, this was really helpful! I'm using p5 to make a dynamic background for my website, and I was having trouble integrating it into a React app. It's very nice to see how the whole process is put together instead of adding yet another dependency to magically make it work.
"I learned very quickly that best option is to actually just learn the tech and not treat it like a black box", this phrase couldn't be more right!
Thank you, this post was very helpful.
Christian would it be possible for you to Git a stub project? Loved the simplicity of your explanations
Thank you so much for the tutorial! I'm wondering have you tried to deploying your website? I'm trying to deploy my website which integrated p5.js with react, but it always shows that "(undefined) ReferenceError: window is not defined". Have you encountered this kind of issue?