DEV Community

Cover image for Integrating P5.js with React
Christian
Christian

Posted on • Edited on

Integrating P5.js with React

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 = () => {
     ...
     }
  }
...
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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>
    )
  }
}

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
rizz0s profile image
Summer Rizzo

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.

Collapse
 
christiankastner profile image
Christian

Awesome! Stoked to help

Collapse
 
dcsan profile image
dc

Unfortunately this approach just keeps creating new instances of a p5 component on each "hot update" when using create-react-app or similar.

Collapse
 
nardove profile image
Ricardo Sanchez • Edited

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:

useEffect(() => {
  const myP5 = new p5(Sketch, myRef.current);
  return myP5.remove;
},[])
Enter fullscreen mode Exit fullscreen mode

This will remove the current p5 instance and creates a new one on the next render

Collapse
 
goksuokar profile image
Goksu

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..

Collapse
 
witherc0d3 profile image
WITHERC0D3

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?

Collapse
 
anshul16 profile image
Anshul Sachdev

import React, { useEffect } from 'react';
import p5 from 'p5';

const App = () => {

const myRef = React.createRef();

const Sketch = (p) => {

 p.setup = () => {
  p.createCanvas(300, 200);
  p.noStroke();
 }

 p.draw = () => {
  p.background('orangered');
  p.ellipse(150, 100, 100, 100);
 }
Enter fullscreen mode Exit fullscreen mode

}

useEffect(() => {
const myP5 = new p5(Sketch, myRef.current)
},[])

return (
  <div ref={myRef}>
    Hello
  </div>
)
Enter fullscreen mode Exit fullscreen mode

}

export default App;

Collapse
 
ndutared profile image
Abby Nduta

Thanks for the response @anshul16. It will surely help someone in the community.

Collapse
 
artydev profile image
artydev

Hello Christian,

Thank you.
You can test your code here P5inReact

Regards

Collapse
 
snoogans775 profile image
Kevin Fredericks • Edited

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.

Collapse
 
dcsan profile image
dc

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....?

Collapse
 
celetra profile image
Celetra

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.

Collapse
 
yesklin profile image
Carlos Daniel

"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.

Collapse
 
adithyavasudevan profile image
adithyavasudevan

Christian would it be possible for you to Git a stub project? Loved the simplicity of your explanations

Collapse
 
iris666 profile image
Iris

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?