2

I'm using ml5.js to train an ML model. I have to add images to the model using the webcam. The train function works fine with the current code. However, when I try to set a state within my if else statement inside the train function, it throws an error when I try to test it using the test button.

the value of classifier becomes undefined.

export const Component: React.FC<ComponentProps> = (props: ComponentProps) => {
    let classifier: any;
         classifier = featureExtractor.classification(capture, videoReady);
    }

    
    function final() {
        classifier.classify(capture, (error, result) => {
            setPrediction(label);
            
        });
    }

    return (<div>
            <Button variant="contained" color="primary" onClick={() => final()}>testing!</Button>
        </div>
    </div>)
        ;
};

classifier is a variable so it would re-render each time. useRef could be used here but I couldn't figure out how.

const classifier = useRef()
classifier.current

accessing it like this still gave me the error. I also tried making a state for the classifier itself but that did not seem to work for me either.

Here's a Codesandbox to try the full thing. To see the error, you can set a state in the if else statement of the train function:

https://codesandbox.io/s/hardcore-solomon-zb34l?file=/src/Component.tsx

  • Any reason you're not using a normal component class for this, given the size of your code? – Mike 'Pomax' Kamermans Apr 22 '21 at 21:46
  • @Jbd no it wouldn't make a difference. You can get the same exact functionality from a functional component. In this case, I believe part of the issue is that your classifier needs to be a ref. A ref's value will stay the same from one render to the next, but a regular variable that you define will change on every single render. Which means if you setState, your classifier immediately becomes undefined. Anywhere that you access classifier will need to be using classifier.current – Chris Apr 22 '21 at 21:50
  • Here you go @Jbd https://codesandbox.io/s/mutable-wildflower-3p306?file=/src/Component.tsx . useRef creates an object with a property called current. current is a mutable object that can potentially be any value you'd like. When you want to access your ref, you use myRef.current. – Chris Apr 22 '21 at 21:59
  • it wouldn't make a difference, it's just a tad odd to see a functional component written in a way that looks like it _really_ wants to be a normal class with normal class functions =) – Mike 'Pomax' Kamermans Apr 22 '21 at 22:24
  • Thanks for taking out time. Unfortunately, this doesn't really fix my problem. So for eg, I want to be able to set the value of **loss** from within the if else statement. I want to use the value of loss later for displaying on the screen. But if I try to set it within the if statement, I will get an error. Similarly, with your current code, the TEST button works once. But if i click on it the second time, it throws the same undefined error @Chris –  Apr 22 '21 at 22:48
  • I see @Jbd, unfortunately I wasn't able to test it that far. I don't have a camera to run the full code :/ – Chris Apr 23 '21 at 16:43
  • `capture` and `classifier` are local variables and don't persist their values across renders. If you want to persist data across multiple renders use `useState` or `useRef`. – 3limin4t0r Apr 25 '21 at 13:47
  • @3limin4t0r I am already aware of that, i tried using both but couldn't make it work according to my use case. As you might also notice from the comments above. Would you mind testing and trying it on the code sandbox link? –  Apr 25 '21 at 13:57
  • I don't like that the snippet is using the web-cam. Surely this is not a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). The problematic behaviour could probably be extracted into a snippet that is a lot smaller/faster. If not, at least remove the web-cam stuff and upload a picture that is used instead. – 3limin4t0r Apr 25 '21 at 14:30
  • @Mike'Pomax'Kamermans I thought functional components were actually meant to replace classes completely, at least, that's how React itself presents it. There is even a whole page dedicated to hooks https://reactjs.org/docs/hooks-intro.html that lists the advantages over using classes. We have moved to functional components and hooks for a while now and make sure to refactor every class we have to turn it into a functional component whenever we can. Functional components are much easier to work with than with classes. And if you think about it, Javascript never trully had classes anyway. – jperl Apr 25 '21 at 15:43
  • Have you considered defining the `classifier` object outside the component but within this same module? That way, re-rendering the component doesn't cause `classifier` to be re-rendered unless you are remounting the component. You might also want to consider refactoring the code after this works – Tolumide Apr 25 '21 at 16:30
  • @jperl note that the page you linked to rather explicitly mentions that [there are no plans to remove classes from React](https://reactjs.org/docs/hooks-intro.html#gradual-adoption-strategy). – Mike 'Pomax' Kamermans Apr 25 '21 at 17:22
  • I believe this was resolved here: https://stackoverflow.com/questions/67255180/display-value-saved-in-useref-variable/67255350#67255350 – Marc Baumbach Apr 25 '21 at 17:32
  • @Mike'Pomax'Kamermans Sure, I didn't say that either. They also said that "hooks work side-by-side with existing code so you can adopt them gradually" and "there is no rush to migrate to Hooks". My point was that, as I said, they're meant to replace classes. Sure, people already have a lot of classes and it would be terrible if all of a sudden, classes would not be supported anymore but hopefully you got my point. – jperl Apr 26 '21 at 07:39

2 Answers2

1

I've provided a forked version of the Codesandbox that was mentioned above in the comments: https://codesandbox.io/s/frosty-sea-0pcfx?file=/src/Component.tsx. This contains a few fixes, but most of it related to changing the local variables for capture and classifier and assigning them to refs. The following code:

let capture: p5Types.Element;
let classifier: any;

was changed to:

let capture: any = useRef<any>();
let classifier: any = useRef<any>();

Then all references to those variables in the other areas of the code were switched to capture.current and classifier.current. Those could potentially be stored in state as well, but they appear to only be assigned and utilized outside of data used during rendering and don't require the component to re-render whenever they are assigned.

Marc Baumbach
  • 10,323
  • 2
  • 30
  • 45
0

I would do:

const { current: heap } = useRef({});

// In your `setup`
heap.classifier = featuresExtractor.classification(..);

// elsewhere access it as
heap.classifier

when you wrote:

const classifier = useRef()
classifier.current

classifier.current is persistent across re-renders, but you still need to asign it in setup as classifier.current = .... I prefer the way I wrote, because heap becomes a convenient place to add any other stuff which should be persistent across re-renders.

Sergey Pogodin
  • 406
  • 2
  • 6