- Vos composants "View" doivent rendre des éléments de conteneur dans lesquels le canevas p5.js doit être inséré.
- Parce que vous avez transmis des tableaux vides à la fonction
useEffect
les appels de fonction, votre View2
n'était pas mis à jour.
- En
props
qui est transmis à la fonction de votre composant est n'a jamais muté. Quand props
changer un nouveau props
est transmis à un nouvel appel à la fonction de votre composant.
- Lorsque les effets doivent être mis à jour, leur fonction de nettoyage est appelée, puis la fonction d'initialisation est à nouveau appelée. Il est donc important de supprimer l'esquisse pendant le nettoyage. En fait, vous devriez toujours le faire, car si votre composant entier est supprimé, vous voulez le faire savoir à p5.js avant que son élément canvas ne soit supprimé du DOM.
Voici un exemple concret :
// 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 : Mise à jour sans suppression
J'étais donc curieux de savoir ce qui se passerait si vous vouliez mettre à jour une esquisse p5.js existante qui a été créée à l'aide de la fonction useEffect()
sans supprimer et recréer l'ensemble du sketch p5.js. Eh bien, désistement : Je ne suis pas un expert de ReactJS mais je pense que la réponse est oui. En gros, vous devez faire ce qui suit :
- Créer une nouvelle variable d'état/méthode de définition pour stocker et définir l'esquisse elle-même.
- Dans le cadre de la
useEffect
qui crée l'esquisse :
- Ne créer l'esquisse que si elle n'existe pas encore
- Lorsque vous créez l'esquisse, utilisez la méthode set pour la stocker dans la variable state.
- Ne pas renvoyer de fonction de nettoyage
- Ajouter un autre effet qui est mis à jour lorsque la variable d'état qui stocke une référence à l'esquisse est mise à jour.
- Cet effet ne doit rien créer, il doit simplement renvoyer une fonction de nettoyage.
- Cette fonction de nettoyage doit supprimer l'esquisse, si elle existe (en utilisant la variable d'état susmentionnée).
- Dans votre fonction de création de croquis, créez une variable locale pour stocker les données d'entrée initiales provenant de
props
- Ajoutez une fonction à votre objet d'esquisse (en attribuant à la fonction
p.updateFn = ...
) qui met à jour ladite variable locale
- Dans votre
useEffect
qui crée le croquis, si le croquis existe déjà, appeler ladite fonction de mise à jour avec la nouvelle valeur de props
.
Je sais que c'est un peu compliqué, mais voici un exemple :
// 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 neds 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>
Si cela va à l'encontre des meilleures pratiques de ReactJS ou sera cassé dans certains scénarios, j'espère que quelqu'un qui connaît React mieux que moi le dira.