score:3

Accepted answer
  1. your "view" components need to render container elements where the p5.js canvas should be inserted.
  2. because you were passing empty arrays to the useeffect function calls, your view2 effect was not getting updated.
  3. the props object that is passed to your component function is never mutated. when props change a new props object is passed to a new call to your component function.
  4. when effects need to be updated their cleanup function is called, and then the initialization function is called again. therefor it is important to remove the sketch during cleanup. in fact you should always do this, because if your entire component is removed you want to let p5.js know that before its canvas element is removed from the dom.

here's a working example:

// this is in place of an import statement
const { useref, usestate, useeffect } = react;

const view1 = props => {
  const containerref = useref();
  
  const sketch = (p) => {
    p.setup = () => {
      p.print("view1 initializing");
      p.createcanvas(200, 100);
    }

    p.draw = () => {
      p.background(150);
    }

    p.mouseclicked = () => {
      p.print('click!');
      // passing information from the child up to the parent
      props.firstviewtoparent(p.map(p.mousex, 0, p.width, 0, 255));
    }
  }

  useeffect(
    () => {
      // make sure the p5.js canvas is a child of the component in the dom
      new p5(sketch, containerref.current);
    },
    // this empty list tells react that this effect never needs to get re-rendered!
    []
  );

  // note: you're going to want to defined an element to contain the p5.js canvas
  return (
    <div ref={containerref}></div>
  );
}

const view2 = props => {
  console.log('view2');
  const containerref = useref();
  
  const sketch = (p) => {
    p.setup = () => {
      p.print("view2 initializing");
      p.createcanvas(200, 100);
    }

    p.draw = () => {
      p.background(props.datafromsibling);
    }
  }

  useeffect(
    () => {
      let inst = new p5(sketch, containerref.current);
      
      // cleanup function! without this the new p5.js sketches
      // generated with each click will just appear one after the other.
      return () => inst.remove();
    },
    // let react know that this effect needs re-rendering when the datafromsibling prop changes
    [props.datafromsibling]
  );

  return (
    <div ref={containerref}></div>
  );
}

function app() {
  const [data, setdata] = usestate(0);

  const firstviewtoparent = (num) => {
    console.log('handling firstviewtoparent callback');
    setdata(num);
  };

  return (
    <div classname="app">
      <view1 firstviewtoparent={firstviewtoparent}/>
      <view2 datafromsibling={data} />
    </div>
  );
}

reactdom.render(
  <react.strictmode>
    <app />
  </react.strictmode>,
  document.getelementbyid('root')
);
.app {
  display: flex;
  flex-direction: row;
}
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
</head>

<body>
  <div id="root"></div>
</body>

</html>

addendum: update without removal

so i was curious: what if you wanted to update an existing p5.js sketch that was created with useeffect() without removing and recreating the entire p5.js sketch. well, disclaimer: i am not a reactjs expert, but i think the answer is yes. basically you need to do the following:

  1. create a new state variable/set method to store and set the sketch itself.
  2. in the useeffect call that creates the sketch:
    1. only create the sketch if it doesn't exist yet
    2. when you create the sketch, use the set method to store it in the state variable.
    3. don't return a cleanup function
  3. add another effect that is updated when the state variable that stores a reference the the sketch is updated
    1. this effect shouldn't create anything, it should just return a cleanup function
    2. this cleanup function should remove the sketch, if it exists (using the aforementioned state variable)
  4. in your sketch creation function, create a local variable to store the initial input data from props
  5. add a function to your sketch object (by assigning p.updatefn = ...) that updates said local variable
  6. in your useeffect call that creates the sketch, if the sketch already exists, call said update function with the new value from props.

kind of complicated i know, but here's an example:

// this is in place of an import statement
const { useref, usestate, useeffect } = react;

const view1 = props => {
  const containerref = useref();
  
  const sketch = (p) => {
    p.setup = () => {
      p.print("view1 initializing");
      p.createcanvas(200, 100);
    }

    p.draw = () => {
      p.background(150);
    }

    p.mouseclicked = (e) => {
      // it turns out that p5.js listens for clicks anywhere on the page!
      if (e.target.parentelement === containerref.current) {
        p.print('click!');
        // passing information from the child up to the parent
        props.firstviewtoparent(p.map(p.mousex, 0, p.width, 0, 255));
      }
    }
  }

  useeffect(
    () => {
      // make sure the p5.js canvas is a child of the component in the dom
      let sketch = new p5(sketch, containerref.current);
      
      return () => sketch.remove();
    },
    // this empty list tells react that this effect never needs to get re-rendered!
    []
  );

  // note: you're going to want to defined an element to contain the p5.js canvas
  return (
    <div ref={containerref}></div>
  );
}

const view2 = props => {
  console.log('view2');
  const containerref = useref();
  const [sketch, setsketch] = usestate(undefined);
  
  const sketch = (p) => {
    let bgcolor = props.datafromsibling;
    p.setup = () => {
      p.print("view2 initializing");
      p.createcanvas(200, 100);
    }

    p.draw = () => {
      p.background(bgcolor);
    }
    
    p.updatebackgroundcolor = function(value) {
      bgcolor = value;
    }
  }

  useeffect(
    () => {
      if (!sketch) {
        // initialize sketch
        let inst = new p5(sketch, containerref.current);
      
        setsketch(inst);
      } else {
        // update sketch
        sketch.updatebackgroundcolor(props.datafromsibling);
      }
      
      // we cannot return a cleanup function here, be cause it would run every time the datafromsibling prop changed
    },
    // let react know that this effect needs re-rendering when the datafromsibling prop changes
    [props.datafromsibling]
  );
  
  useeffect(
    () => {
      // this effect is only responsible for cleaning up after the previous one 😅
      return () => {
        if (sketch) {
          console.log('removing sketch!');
          // removing p5.js sketch because the component is un-mounting
          sketch.remove();
        }
      };
    },
    // this effect needs to be re-initialized *after* the sketch gets created
    [sketch]
  );

  return (
    <div ref={containerref}></div>
  );
}

function app() {
  const [data, setdata] = usestate(0);
  const [showsketch, setshowsketch] = usestate(true);

  const firstviewtoparent = (num) => {
    console.log('handling firstviewtoparent callback');
    setdata(num);
  };

  return (
    <div classname="app">
      <view1 firstviewtoparent={firstviewtoparent}/>
      
      {showsketch && <view2 datafromsibling={data} />}
      <button onclick={() => setshowsketch(showsketch ? false : true )}>
        toggle
      </button>
    </div>
  );
}

reactdom.render(
  <react.strictmode>
    <app />
  </react.strictmode>,
  document.getelementbyid('root')
);
.app {
  display: flex;
  flex-direction: row;
}
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
</head>

<body>
  <div id="root"></div>
</body>

</html>

if this violates any reactjs best practices or will be broken in some scenarios hopefully somebody who knows react better than me will chime in.

addendum #2: uselayouteffect

i have recently learned that this sort of effect, which adds a new element to the dom, should generally be initialized with uselayouteffect instead of useeffect. this is because useeffect runs in parallel with the update to the dom and does not block rendering, whereas uselayouteffect does block rendering. so when you modify the dom in useeffect (by telling p5.js to create a canvas element) it may result in objects in the page flickering or suddenly moving from where they are initially positioned. once i have more time to test this out i will update the code examples above.


Related Query

More Query from same tag