score:3
- your "view" components need to render container elements where the p5.js canvas should be inserted.
- because you were passing empty arrays to the
useeffect
function calls, yourview2
effect was not getting updated. - the
props
object that is passed to your component function is never mutated. whenprops
change a newprops
object is passed to a new call to your component function. - 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:
- create a new state variable/set method to store and set the sketch itself.
- in the
useeffect
call that creates the sketch:- only create the sketch if it doesn't exist yet
- when you create the sketch, use the set method to store it in the state variable.
- don't return a cleanup function
- add another effect that is updated when the state variable that stores a reference the the sketch is updated
- this effect shouldn't create anything, it should just return a cleanup function
- this cleanup function should remove the sketch, if it exists (using the aforementioned state variable)
- in your sketch creation function, create a local variable to store the initial input data from
props
- add a function to your sketch object (by assigning
p.updatefn = ...
) that updates said local variable - in your
useeffect
call that creates the sketch, if the sketch already exists, call said update function with the new value fromprops
.
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.
Source: stackoverflow.com
Related Query
- p5.js and React: how to update a sketch inside a component when one of the component's props is updated
- How can I use useEffect in React to run some code only when the component first mounts, and some other code whenever an event occurs (repeatedly)?
- react Portal - how to close modal when clicking outside of modal and how to assign ref to modal inside parent component rendering modal?
- How do I pass a prop to a react component yet not update that prop in the child when parent changes?
- how to change the style of only one react component that is being render through .map() when the component is clicked
- React Hooks, useState related question. How to update one property of object and keep the rest
- React Router v6 : How to render multiple component inside and outside a div with the same path
- How to refresh the table data when one of the item has its valid till date and time has expired in React Js
- How to update a useState which has an array, inside this array I have objects this objects will update when the input value will change in react js
- React - How to update my component when there is a change on the server
- How to define TypeScript types when used with React useState and the previous state inside a state updating function?
- How can I stop the Select component from closing when I clicked one of its item(Input element) - react antd
- How to update react component state from one component to another, and call any functions from any component
- REACT JS How to show one component and hide the others on click
- How to create multiple swiper instance in a react component and control one with the swipe of another?
- How to use AND and OR statement inside the component while comparing in react component
- how can i loop through the array inside of an object. and render it in the react component
- How to preserve value in react component even when the component updates and rerenders
- How to set state in parent from one child component and access the state in another child component using react and typescript?
- How to Update the parent component when child component updates in React
- How to test the style inside the CSS class for React component using Jest and Enzyme?
- How to get all the child elements inside a component in unit test using react and enzyme?
- Will the background component unmount when modal is open in react? If not, how to maintain/store states of modal and react component using redux?
- How do I make the API rerender and update the component when the search field is updated in React?
- How can I update State in React using the React-Select Component and User Selection?
- Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component when using Create React App and Formik
- React : All the Modal gets opened at once and only last one shows up when I'm passing props inside modal
- How to update the state when 1 option in tag select is clicked and navigte to another screen in React Js
- How can I make my React Component mount, when the right url is accessed and the user is not logged in?
- How to get the data from dropdown component in React and update state?
More Query from same tag
- Using multiple react apps in page with relevant stores
- A proper way to make React app Multilingual
- TODOLIST Update in REACT
- fetch inside reducer is not working if I use too many .thens
- Integrate NodeJS MySql with React Application
- TailwindCSS and React Not Correctly Importing JSX Elements
- Typescript: Pattern for building an object one property at a time
- React create one styled components for two different icons?
- Preserve React component state with Relay.setVariables
- React router v6: how to get relative index path in component?
- update function returned by useState hook not updating the value
- How do I route between pages in Embedded React App?
- Uncaught TypeError: Cannot read property 'go' of undefined for history.go(-1)
- redux Array in state got undefined after reducer finish
- Redux action not firing - no errors
- How to stop random links from opening with react-router?
- How to access a function in a Webpack bundle from a HTML script
- Why is React treating my array as if it were empty?
- NextJs routing to the same page with different query
- Using ts-jest with create-react-app
- Shuffle.js implementation with React.js
- Is it bad practice to set HTML tags in React rendering to null?
- Calculating time difference for sets of streaming data
- Search multiple domains with material-ui autocomplete
- Where is the best place to make API calls in a react/redux/reach router application?
- Gatsbyjs and internal links
- React.js render json response, co fetch or axios
- Reactjs client for Swagger / OpenAPI API specification
- react-native android error Failed to install the app native_modules.gradle' line: 170
- What's the best way to prevent React app being scraped?