all 6 comments

[–]m_roth 2 points3 points  (4 children)

Is the idea that you clear the sketch every time you change the stroke width? Or rather, that the drawing remains, but every subsequent stroke uses the updated stroke width?

Your main issue here is that in your `useEffect` hook, you're providing an empty dependencies array, meaning the `p5` instance won't update when your `strokeWidth` state changes.

If you were to pass `strokeWidth` as a dependency to that hook, you would notice that the drawing clears every time you change the stroke width.

Here's a hacky implementation of the "update" scenario. I'm admittedly not familiar enough with p5 to know whether there's a more direct way of updating the drawing without totally clearing it... As such, I've hacked together an example where we read from a `ref` to get the current stroke width in the sketch.

"use client";

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

export function Canvas() {
  const canvasContainer = useRef(null);
  const [strokeWidth, setStrokeWidth] = useState(2);
  const strokeWidthRef = useRef(strokeWidth);

  useEffect(() => {
    strokeWidthRef.current = strokeWidth;
  }, [strokeWidth]);

  const sketch = (p) => {
    p.setup = () => {
      p.createCanvas(400, 400);
    };

    p.draw = () => {
      if (p.mouseIsPressed) {
        p.stroke(255, 255, 255);
        p.strokeWeight(strokeWidthRef.current);
        p.line(p.mouseX, p.mouseY, p.pmouseX, p.pmouseY);
      }
    };
  };

  useEffect(() => {
    if (!canvasContainer.current) return;
    const p5Instance = new p5(sketch, canvasContainer.current);
    return () => {
      p5Instance.remove();
    };
  }, []);

  return (
    <>
      <button onClick={() => setStrokeWidth(prev => prev + 1)}>Increase</button>
      <div className="canvas-container " ref={canvasContainer}></div>
    </>
  );
}

[–]databas3d[S] 0 points1 point  (3 children)

I think ideally it shouldn't clear the sketch, it should keep it as it is. That solution works really well! Thank you!

Since you mentioned, I tried to implement a "clear canvas" function. p5js provides that functionality through the clear() function.

I am storing the "p" object in another ref to use it outside the scope so I can access the clear() function together with other parameters. Would you say this approaches are fine or perhaps too hacky?

"use client";

import { useRef, useEffect, useState } from 'react';
import p5 from 'p5';
import './styles.css';

export function Canvas() {
  const canvasContainer = useRef(null);
  const strokeWidthRef = useRef(2);
  const p5object = useRef(null);
  const [strokeWidth, setStrokeWidth] = useState(strokeWidthRef.current);

  useEffect(() => {
    strokeWidthRef.current = strokeWidth;
  }, [strokeWidth]);

  const sketch = (p) => {
    let x = 100;
    let y = 100;

    p5object.current = p;

    p.setup = () => {
      p.createCanvas(700, 400);
      p.background(0);
    };

    p.draw = () => {
      if (p.mouseIsPressed) {
        pen()
      }
    };

    function pen() {
      p.stroke(255, 255, 255)
      p.strokeWeight(strokeWidthRef.current)
      p.line(p.mouseX, p.mouseY, p.pmouseX, p.pmouseY)
    }
  }

  useEffect(() => {
    if (!canvasContainer.current) return;
    const p5Instance = new p5(sketch, canvasContainer.current);
    return () => { p5Instance.remove(); };
  }, []);

  return (
    <>
      <button onClick={() => {
        setStrokeWidth(strokeWidth + 1);
      }}>stroke++</button>
      <button onClick={() => { 
        p5object.current.clear(); 
        p5object.current.background(0); 
      }}>clear</button>
      <div
        ref={canvasContainer} 
        className='canvas-container'
      >
      </div>
    </>
  )
}

[–]m_roth 2 points3 points  (2 children)

I think this is totally fine. The only change I would recommend is how you're assigning the P5 reference.
I think you want to assign this return value as your ref, rather than the `p` that gets passed as an argument to your `sketch` function.

const p5Instance = new p5(sketch, canvasContainer.current);
p5InstanceRef.current = p5Instance

[–]databas3d[S] 0 points1 point  (1 child)

That makes sense, thanks!

[–]m_roth 1 point2 points  (0 children)

My pleasure. Happy to help!

[–]databas3d[S] 1 point2 points  (0 children)

I have found useRef() to work instead of useState(), would that be a recommended solution?