0

I have a variable

const prediction = useRef<any>(null);

I click on a button that runs a function which sets the value of my variable:

    function showResult() {
        classifier.current.classify(capture, (result) => {
            prediction.current = result[0].label
            console.log('prediction.current', prediction.current)

        });
    }

On the console log, I see the correct value for prediction.current. However, when I try to render it in JSX, I don't get anything. What can I do to change this?

If I use setState inside the classifier.current.classifyfunction, it gives me different. It's an ml5 function. Is there an alternate way? Could i somehow setState outside the function? useEffect maybe?

    return (
    <div>
            <Button variant="contained" color="primary" onClick={() => gotResult()}>Test!</Button>
            <br />
            <span>Prediction: {prediction.current}</span><br />
        </div>
    </div>)
    //const [prediction, setPrediction] = useState<string>();
    //const [confidence, setConfidence] = useState<string>();
    //const [imageClassifier, setClassifier] = useState<any>();

    let capture: p5Types.Element;
    const classifier = useRef<any>(null);
    const prediction = useRef<any>(null);
    const confidence = useRef<any>(null);
    const setup = (p5: p5Types, canvasParentRef: Element) => {
        capture = p5.createCapture(p5.VIDEO).parent(canvasParentRef);
        const featureExtractor = ml5.featureExtractor('MobileNet', modelReady);
        classifier.current = featureExtractor.classification(capture, videoReady);
        console.log('start', classifier);
    }

    function showResult() {
        console.log('classifier in results', classifier);
        classifier.current.classify(capture, (result) => {
            prediction.current = result[0].label;
            console.log(result[0].confidence); // Should output 'dog'

        });
    }

  • 1
    A ref changing will not cause a component to re-render, which is why you probably aren't seeing the value displayed. Is there any reason to not just store that in `useState`? That would trigger the re-render. – Marc Baumbach Apr 25 '21 at 15:39
  • If I use setState inside the ```classifier.current.classify```function, it gives me different. It's an ml5 function. Is there an alternate way? Could i somehow setState outside the function? useEffect maybe? @MarcBaumbach –  Apr 25 '21 at 15:41
  • @Jdb I'm not sure I follow, but if you have access to the ref from `useRef` in that function, you should be able to call the `useState`'s setter function just fine. The setter function isn't really special other than it automatically triggers a re-render of the component that it's defined in, so you should be able to pass it to whatever is defining that ml5 function similar to however you're getting the ref there. – Marc Baumbach Apr 25 '21 at 15:45

1 Answers1

0

A ref changing its current value does not trigger any re-renders in React components, but you should be able to replace the snippets above and trigger the re-render to see the latest value:

const prediction = useRef<any>(null);

Becomes:

const [prediction, setPrediction] = useState(null);

The getResult function would then look like:

function gotResult() {
    classifier.current.classify(capture, (err: any, result: any) => {
        setPrediction(result[0].label])
    });
}

Finally the rendering:

return (
    <div>
        <Button variant="contained" color="primary" onClick={() => gotResult()}>Test!</Button>
        <br />
        <span>Prediction: {prediction}</span><br />
    </div>
)

Your classifier is setting the current value on every render. You probably only want that whenever featureExtractor, capture or videoReady changes. You can do that with useMemo:

const classifier = useMemo(() => {
    return featureExtractor.classification(capture, videoReady);
}, [featureExtractor, capture, videoReady]);

That dependency array in useMemo will make sure the classifier only gets defined if those variables change and not on every render.

Marc Baumbach
  • 10,323
  • 2
  • 30
  • 45
  • Your classifier looks like it's getting re-assigned on every render. You may need to wrap that in a useMemo (I added that as well). – Marc Baumbach Apr 25 '21 at 16:02
  • but I cannot use useMemo inside the setup function as that would throw an error. i updated detailed code above. –  Apr 25 '21 at 16:12
  • What calls `setup`? You might need to provide a complete [MRE](https://stackoverflow.com/help/minimal-reproducible-example) or a link to a codesandbox that reproduces it. – Marc Baumbach Apr 25 '21 at 16:20
  • Here's my initial problem along with a codesandbox: https://stackoverflow.com/questions/67221199/useref-state-becomes-undefined-after-rendering –  Apr 25 '21 at 16:24
  • @Jbd I took a stab at modifying the codesandbox in a fork. I'm not sure it's fixed anything with the ML side of things, but I think the hooks (state + ref) stuff looks correct to me. You might give it a shot: https://codesandbox.io/s/frosty-sea-0pcfx?file=/src/Component.tsx – Marc Baumbach Apr 25 '21 at 16:36
  • Oh yes, it seems to work!! but the only different thing is that you added REF for capture too, right? Along with adding states for prediction. Or did you change something else too? –  Apr 25 '21 at 16:46
  • Also, i'm planning to add a button which makes resets the classifier so I can do the whole training again. How can I reset the classifier now? –  Apr 25 '21 at 16:47
  • @Jbd, yes the capture was also needing to be assigned and was all done in the `setup`, so I assigned it to a ref. I suspect, if you want to reset everything, you may need to call `setup` again, though I'm not sure how that `Sketch` component works and whether that'll actually work if you call it or if you need to somehow reset the `Sketch` too. I'm just not familiar with the other technologies there, just React. :) Best of luck! – Marc Baumbach Apr 25 '21 at 16:49
  • If you want, you could post the Codesandbox link in the original other qs, it has a bounty –  Apr 25 '21 at 17:51