-1

I am trying to learn react and created a sample app that pulls random dog pictures in from the dog.ceo API.

By default a random image of any dog from any breed is returned. However, there is also a list of breeds that comes from the same API that the user can click to switch the breed they want to see.

So in the BreedList component when someone clicks on one of the breeds the breed they selected is sent back up to the App component using the handleBreedChange() event. This same function then sets the dogBreed state and calls back another function (this.getRandomImage()) to display the actual image using the new dog Breed state.

    handleBreedChange(e) {
        //this.setState({ dogURL: e });
        var CurrentURL = `https://dog.ceo/api/breed/${e}/images/random`;
        this.setState({ dogBreed: e });
        this.setState({ dogURL: CurrentURL });
        console.log(e);
        console.log(this.state);
        this.getRandomImage();
    }

However, I'm running into a weird issue where on the first call the dogBreed state is not updated. It only updates on the second call (the user needs to make 2 actions before the state is current). So when the page is loaded dogBreed is blank. When a breed is clicked it stays blank on the first event call and only updates on the second one.

I read an article on how state updates are async so I imagine that has something to do with it: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

But I'm not sure how to ensure that the state is fully updated before calling the getRandomImage function (assuming that's actually my issue).

Here is my full code on Codesandbox: https://codesandbox.io/p/github/Pawel-IT/thinking_in_react/main?file=%2Fsrc%2FApp.js

Edit:

Based on what Ray suggested below I passed the expected URL as a argument to the getRandomImage function and if it's set use that instead of the state and that works. Thanks Ray! Is this the best way to do it? Would be nice is there was some function that forced the state to be updated? Or should I not be using the state for this at all?

handleBreedChange(e) {
    //this.setState({ dogURL: e });
    var CurrentURL = `https://dog.ceo/api/breed/${e}/images/random`;
    this.setState({ dogBreed: e });
    this.setState({ dogURL: CurrentURL });
    console.log(e);
    console.log(this.state);
    this.getRandomImage(e);
}

getRandomImage(breed) {
    var breedURL = this.state.dogURL;
    if (breed) {
        breedURL = `https://dog.ceo/api/breed/${breed}/images/random`;
    }

    axios.get(breedURL).then((response) => {
        //setPosts(response.data);
        this.setState({ message: response.data.message });
    });
}
Pawel K
  • 5
  • 3
  • 1
    This is similar to https://stackoverflow.com/questions/69333367/useeffect-not-getting-called-immedietly-as-dependency-value-changes/69333445#69333445 – potatoxchip Dec 18 '22 at 20:18

2 Answers2

0

State won’t be updated until the next component render. getRandomImage won’t have the updated state prior to that. If it needs the new value you could pass it as an argument, or as a callback to setState.


Update:

I'm not sure what you mean by "force state to update", but there are numerous ways to handle what you're trying to do, including the suggestions in my answer and elsewhere.

It's not clear that you need state for the breed image at all here. Why not have a <RandomBreedImage breed={this.state.breed} /> component and let that component handle it?

Your handleBreedChange state update will trigger a re-render, and RandomBreedImage will get the new prop.

ray
  • 26,557
  • 5
  • 28
  • 27
  • Thank you! Is there a way to force the state to update? I changed my code to pass the breed to the getRandomImage function and it does work. But I'm wondering if there is a cleaner way to force the state to update? – Pawel K Dec 18 '22 at 20:33
0

Why not pass the updated state as a parameter to the getRandomImage function.

   this.getRandomImage({ dogBreed: e,
 dogURL: CurrentURL });

update:

You can't 'force state to update' however you can wait for it to update. If you are looking for this function to be ran every time the state updates you could use a componentDidUpdate method.

Example:

componentDidUpdate(_prevProps, prevState){
    // only runs if the value of the dogBreed state changes.
    if(this.state.dogBreed !== prevState.dogBreed)
         this.getRandomImage()
}

This method would be ran every time the state or the props of the component updates. If your goal is to have the this.getRandomImage() method executed everytime the dogBreed state update I believe this would be the best way of going about it.

Read more about the componentDidUpdate method and the react lifecycle here https://reactjs.org/docs/react-component.html#componentdidupdate

toby
  • 121
  • 5
  • I did that based on what Ray suggested above and it does work. Thanks! But my thinking was since this could come from various components as the application grows having it a state made more sense. But maybe I'm just needlessly overcomplicating things and there is no point of using the state in this case? – Pawel K Dec 18 '22 at 20:43
  • if you need to persist the values across rerenders then it makes sense to use state but in this case it is much better to pass it as an argument. – toby Dec 18 '22 at 20:46
  • see my updated response, I think I've put what you're looking for. – toby Dec 18 '22 at 22:21