Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing data from one function to another in React

So I am using P5, and ML5 and I am working with them in React, so it kind of makes me jump over some hurdles to get things working.

I am trying to get the label name, to draw on the webcam. I can get the console.log to log the label name in the gotResults function, but I am not sure how I would go about using this to get it to work outside of gotResults..

Still new to coding so forgive me :)

    import React, { useRef, useEffect, useState } from 'react';
import p5, { VIDEO } from 'p5'
import ml5 from 'ml5'


const Webcam = props => {
    const myRef = useRef();
    const [label, setLabel] = useState([
        { label: 'Starting Label', confidence: 0 },
    ]);

    useEffect(() => {
        let myp5 = new p5(sketch, myRef.current)
    });


    const sketch = (p) => {


        let mobilenet;
        let cam;
        let classifier;
        let thumbUpRight;
        let thumbDownRight;
        let thumbUpLeft;
        let thumbDownLeft;
        let trainButton;
        // let label = '';


        const modelReady = () => {
            console.log('Model is ready!');
        }

        const videoReady = () => {
            console.log('Video is ready!');
        }

        const whileTraining = (loss) => {
            if (loss == null) {
                console.log('Training Complete')
                classifier.classify(gotResults)
            } else {
                console.log(loss)
            }
        }



        const gotResults = (error, result) => {
            if (error) {
                console.error(error);
            } else {
                // console.log(result)
                setLabel(result);
                classifier.classify(gotResults);
            }

        }



        p.setup = () => {
            p.createCanvas(640, 550);
            cam = p.createCapture(VIDEO);
            cam.hide();
            p.background(0)
            mobilenet = ml5.featureExtractor('MobileNet', modelReady)
            classifier = mobilenet.classification(cam, videoReady)

            thumbUpRight = p.createButton('Up Right');
            thumbUpRight.mousePressed(function () {
                classifier.addImage('thumbUpRight');
            })

            thumbDownRight = p.createButton('Down Right');
            thumbDownRight.mousePressed(function () {
                classifier.addImage('thumbDownRight');
            })

            thumbUpLeft = p.createButton('Up Left');
            thumbUpLeft.mousePressed(function () {
                classifier.addImage('thumbUpLeft');
            })


            thumbDownLeft = p.createButton('Down Left');
            thumbDownLeft.mousePressed(function () {
                classifier.addImage('thumbDownLeft');
            })


            trainButton = p.createButton('Train');
            trainButton.mousePressed(function () {
                classifier.train(whileTraining);
            })
        };


        p.draw = () => {

            p.background(0);
            p.image(cam, 0, 0)
            p.fill(255);
            p.textSize(32);
            p.text(label[0].label, 10, 530);
        }

    };

    return (
        <div ref={myRef}>

        </div>
    )
}

export default Webcam;

EDIT: So the error is something to do with the state resetting, and re-rendering the webcam, and everything on each call to ML5? I am guessing, it's like redrawing each time it hits the ML5 api.. ?

like image 251
Blue Avatar asked Sep 27 '26 23:09

Blue


2 Answers

It seems that these re-renders are happening because the classifier.classify(gotResults) function will execute many times and there you're updateting the state using setLabel(result) which will cause a re-render evetime it's being called. You can comment that and check if it's the cullprit. If it is, then you should shallow compare results with the label and only update the state setLabel(result) if these objects are different:

Shallow compare result with label before updating the state:

const gotResults = (error, result) => {
  if (error) {
    console.error(error);
  } else {
    if (!areEqualShallow(label, result) {
      setLabel(result);
    }
    classifier.classify(gotResults);
  }

}

function areEqualShallow(a, b) {
  for (var key in a) {
    if (a[key] !== b[key]) {
      return false;
    }
  }
  return true;
}

If that's not the culprit of these re-renders, then you should check you useEffect hook. I don't know if you do it intentionally, but having a useEffect hook without any arguments, it will have that hook execute on each render.

Using the Effect Hook

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.

This hook will run on each render:

useEffect(() => {
  let myp5 = new p5(sketch, myRef.current)
});

Add an empty array to the end of the useEffect hook to run it only once:

useEffect(() => {
  let myp5 = new p5(sketch, myRef.current)
}, []);

UPDATE

To update the label on your existing P5 object, you'll have to use redraw. You have to create an effect hook on the label state variable which it will be called when the label changes:

// Set a P5 ref so you can use it again
const p5Ref = useRef();

// Change the initial hook to set the p5Ref
useEffect(() => {
  p5Ref.current = new p5(sketch, myRef.current);
}, []);

// This effect will run only when the whole label object is updated,
// not just when a label.<prop> is updated
useEffect(() => {
  // I'm not sure if that's going to work,
  // you'll have to test and see how to force redraw using the P5 returned object properly

  if (p5Ref && p5Ref.current) {
    p5Ref.current.redraw();
  }
}, [label]);

Of course this hook will not trigger if you just change a label value due to object reference, so you'll have to create a new object when you update the label using the setLabel like this:

const gotResults = (error, result) => {
  if (error) {
    console.error(error);
  } else {
    // Create a new object so we'll get the update effect hook to trigger on label change
    setLabel({ ...result });
    classifier.classify(gotResults);
  }
}
like image 161
Christos Lytras Avatar answered Sep 29 '26 13:09

Christos Lytras


Can you have label in state?

const [label, setLabel] = useState('')

and

const gotResults = (error, results) => {
    if (error) {
        console.error(error);
    } else {
        // console.log(results)
        setLabel(results[0].label)
        mobilenet.predict(gotResults)
    }
}

Then, you can use label anywhere in the Webcam function.

like image 29
twharmon Avatar answered Sep 29 '26 13:09

twharmon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!