Search code examples
reactjsclassp5.jsreact-functional-component

How to declare class when using p5.js with react


I am trying to implement the code below in my react app. And I am not sure how to deal(?) with class Walker.

// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com

let walker;

function setup() {
  createCanvas(640,360);
  walker = new Walker();
  background(127);
}

function draw() {
  walker.step();
  walker.render();
}

class Walker {
  constructor(){
    this.x = width/2;
    this.y = height/2;
  }

  render() {
    stroke(0);
    point(this.x,this.y);
  }

  step() {
    var choice = floor(random(4));
    if (choice === 0) {
      this.x++;
    } else if (choice == 1) {
      this.x--;
    } else if (choice == 2) {
      this.y++;
    } else {
      this.y--;
    }
    this.x = constrain(this.x,0,width-1);
    this.y = constrain(this.y,0,height-1);
  }
}

Currently I wrote the code as below:

import React, { useRef, useEffect } from "react";
import p5 from "p5";

let width;
let height;

class Walker {
  constructor(p5) {
    this.x = width / 2;
    this.y = height / 2;
  }

  render(p5) {
    p5.stroke(0);
    p5.point(this.x, this.y);
  }

  step(p5) {
    let choice = Math.floor(Math.random(4));
    if (choice === 0) {
      this.x++;
    } else if (choice === 1) {
      this.x--;
    } else if (choice === 2) {
      this.y++;
    } else {
      this.y--;
    }
    this.x = p5.constrain(this.x, 0, width - 1);
    this.y = p5.constrain(this.y, 0, height - 1);
  }
}

const App = () => {
  const processingRef = useRef();
  let walker;

  const Sketch = (p) => {
    p.setup = () => {
      p.createCanvas(640, 360);
      walker = new Walker(p5);
      p.background(127);
    };

    p.draw = () => {
      walker.step();
      walker.render();
    };
  };
  useEffect(() => {
    new p5(Sketch, processingRef.current);
  }, []);

  return <div ref={processingRef} />;
};

export default App;


I got TypeError: Cannot read property 'constrain' of undefined. I would like to know how to fix this problem!


Solution

  • I cannot comment on whether this pattern for using p5.js with ReactJS is a good approach, however, it looks like there is an issue with your conversion to instance mode:

    class Walker {
      // I think you want to assign p5 (possibly rename to p for consistency)
      // to be an instance property (i.e. this.p = p)
      constructor(p5) {
        // You declare width and height above, but they're never assigned.
        // You should use this.p.width and this.p.height instead
        this.x = width / 2;
        this.y = height / 2;
    
        // i.e.: this.p = p5;
      }
    
      // Get rid of the p5 argument and use the instance property this.p
      render(p5) {
        // i.e. Change this:
        p5.stroke(0);
        // to this:
        // this.p.stroke(0);
        // ...
      }
    
      step() {
        // Math.random does not work like the p5.js random function, it does not take
        // parameters. You'll either need to switch this to this.p.random or use
        // Math.random() * 4
        let choice = Math.floor(Math.random(4));
        // ...
      }
    }
    
    const App = () => {
      // ...
    
      const Sketch = (p) => {
        p.setup = () => {
          p.createCanvas(640, 360);
          // I think you mean to be passing the p instance to the Walker constructor,
          // not the p5 namespace object (which will only contain classes)
          walker = new Walker(p5);
          p.background(127);
        };
    
        // ...
      };
      // ...
    };
    

    And, because I was curious about embedding simple ReactJS apps as Stack Snippets, here's a working version:

    class Walker {
      constructor(p) {
        this.x = p.width / 2;
        this.y = p.height / 2;
        this.p = p;
      }
    
      render() {
        this.p.stroke(0);
        this.p.point(this.x, this.y);
      }
    
      step() {
        let choice = Math.floor(this.p.random(4));
        if (choice === 0) {
          this.x++;
        } else if (choice === 1) {
          this.x--;
        } else if (choice === 2) {
          this.y++;
        } else {
          this.y--;
        }
        this.x = this.p.constrain(this.x, 0, this.p.width - 1);
        this.y = this.p.constrain(this.y, 0, this.p.height - 1);
      }
    }
    
    const Sketch = (p) => {
      let walker;
    
      p.setup = () => {
        p.createCanvas(640, 360);
        walker = new Walker(p);
        p.background(127);
      };
    
      p.draw = () => {
        walker.step();
        walker.render();
      };
    };
    
    // Switching to class style component because useEffect is only
    // available in ReactJS >= 16.8.x and Stack Snippets don't have
    // support yet
    class App extends React.Component {
      constructor() {
        super();
        // Switching to React.createRef 
        this.processingRef = React.createRef();
      }
    
      componentDidMount() {
        new p5(Sketch, this.processingRef.current);
      }
      componentDidUpdate() {
        // TODO: cleanup old sketches
        new p5(Sketch, this.processingRef.current);
      }
    
      render() {
        return <div ref={this.processingRef} />;
      }
    }
    
    ReactDOM.render(
     <App />,
     document.getElementById("root")
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>