useEffect and other functions

I got a fetching function for a remote API , and i invoke the fetching inside a useEffect function with empty dependencies , so it can run only once when the application is loaded , the things is there are other functions in the component which are dependent on the outcome of the fetching , so whenever i load the page i get undefined warning page , someone told me is because the useEffect functions runs after the compound is loaded, so it set to invoke the other functions before the fetching function , which is wrong in my case . is it true and how i can over come this issue?

function App() {
  const location = useLocation();
  const [countries, setCountries] = useState([]);
  const [score, setScore] = useState(0);

  const fetchCountries = async () => {
    try {
      const response = await fetch(
        "https://cors-anywhere.herokuapp.com/https://restcountries.eu/rest/v2/all"
      );
      console.log(response);
      const data = await response.json();
      setCountries(data);
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    fetchCountries();
  }, []);

  const getOptions = () => {
    const optionsArray = [
      countries[Math.floor(Math.random() * 250)],
      countries[Math.floor(Math.random() * 250)],
      countries[Math.floor(Math.random() * 250)],
      countries[Math.floor(Math.random() * 250)],
      countries[Math.floor(Math.random() * 250)],
    ];
    return optionsArray;
  };

  const CountryOptions1 = getOptions();
  const CountryOptions2 = getOptions();
  const CountryOptions3 = getOptions();
  const CountryOptions4 = getOptions();
  const CountryOptions5 = getOptions();

  const { name: CountryAName, flag: CountryAFlag } = CountryOptions1[0];
  const { name: CountryBName } = CountryOptions1[1];
  const { name: CountryCName } = CountryOptions1[2];
  const { name: CountryDName } = CountryOptions1[3];

  const { name: CountryEName, flag: CountryEFlag } = CountryOptions2[0];
  const { name: CountryFName } = CountryOptions2[1];
  const { name: CountryGName } = CountryOptions2[2];
  const { name: CountryHName } = CountryOptions2[3];

  const { name: CountryIName, flag: CountryIFlag } = CountryOptions3[0];
  const { name: CountryJName } = CountryOptions3[1];
  const { name: CountryKName } = CountryOptions3[2];
  const { name: CountryLName } = CountryOptions3[3];

  const { name: CountryMName, flag: CountryMFlag } = CountryOptions4[0];
  const { name: CountryNName } = CountryOptions4[1];
  const { name: CountryOName } = CountryOptions4[2];
  const { name: CountryPName } = CountryOptions4[3];

  const { name: CountryQName, flag: CountryQFlag } = CountryOptions5[0];
  const { name: CountryRName } = CountryOptions5[1];
  const { name: CountrySName } = CountryOptions5[2];
  const { name: CountryTName } = CountryOptions5[3];

  const Questions = [
    {
      image: CountryAFlag,
      options: [
        { Answertext: CountryAName, IsitCorrect: true },
        { Answertext: CountryBName, IsitCorrect: false },
        { Answertext: CountryCName, IsitCorrect: false },
        { Answertext: CountryDName, IsitCorrect: false },
      ],
    },
    {
      image: CountryEFlag,
      options: [
        { Answertext: CountryGName, IsitCorrect: false },
        { Answertext: CountryFName, IsitCorrect: false },
        { Answertext: CountryEName, IsitCorrect: true },
        { Answertext: CountryHName, IsitCorrect: false },
      ],
    },
    {
      image: CountryIFlag,
      options: [
        { Answertext: CountryJName, IsitCorrect: false },
        { Answertext: CountryIName, IsitCorrect: true },
        { Answertext: CountryKName, IsitCorrect: false },
        { Answertext: CountryLName, IsitCorrect: false },
      ],
    },
    {
      image: CountryMFlag,
      options: [
        { Answertext: CountryPName, IsitCorrect: false },
        { Answertext: CountryNName, IsitCorrect: false },
        { Answertext: CountryOName, IsitCorrect: false },
        { Answertext: CountryMName, IsitCorrect: true },
      ],
    },
    {
      image: CountryQFlag,
      options: [
        { Answertext: CountryQName, IsitCorrect: true },
        { Answertext: CountryRName, IsitCorrect: false },
        { Answertext: CountrySName, IsitCorrect: false },
        { Answertext: CountryTName, IsitCorrect: false },
      ],
    },
  ];

  const QuestionsList = Questions.map((q) => ({
    ...q,
    options: q.options.sort(() => 0.5 - Math.random()),
  }));

  console.log(QuestionsList[0].options.sort((a, b) => 0.5 - Math.random()));

  return (
    <div className="App">
      <AnimatePresence exitBeforeEnter>
        <Switch location={location} key={location.key}>
          <Route path="/" exact>
            <Starting />
          </Route>
          <Route path="/instructions">
            <Instructions />
          </Route>
          <Route path="/Question">
            <Question
              score={score}
              setScore={setScore}
              QuestionsList={QuestionsList}
            />
          </Route>
          <Route path="/finalscore">
            <FinalScore score={score} setScore={setScore} />
          </Route>
        </Switch>
      </AnimatePresence>
    </div>
  );
}

export default App;

I think we would need to see the code.

edited my question to include the code

That is not enough code to understand. Can we see the entire component?

Is it because countries has no data? In a component that has async data like this, you need to build it to handle edge cases, like pre-load, no data, failure, etc.

And what is the specific error that you are getting. I don’t know what “undefined warning page” is.

i edited the post the post to include the entire component code and below is the warning message i got when i loaded the app.

Blockquote
TypeError: Cannot destructure property ‘name’ of ‘CountryOptions1[0]’ as it is undefined.

App

C:/Users/toshiba/Desktop/Web design/world-flag-quiz/src/App.js:49

  46 | const CountryOptions4 = getOptions();  47 | const CountryOptions5 = getOptions();  48 | > 49 | const { name: CountryAName, flag: CountryAFlag } = CountryOptions1[0];     | ^  50 | const { name: CountryBName } = CountryOptions1[1];  51 | const { name: CountryCName } = CountryOptions1[2];  52 | const { name: CountryDName } = CountryOptions1[3];
const { name: CountryAName, flag: CountryAFlag } = CountryOptions1[0]; 

OK, I guess this is the line. It seems to be saying that CountryOptions1[0] (not sure why that is PascalCase) is undefined. You can never, ever try to access a property or method or undefined or null - that causes a crash in JS - and many other languages.

So, just basic trouble shooting here, when you see a message like:

TypeError: Cannot destructure property ‘name’ of ‘CountryOptions1[0]’ as it is undefined.

if you can’t tell what the problem is by looking at the code, you should log that out and see what it is. You should do something like:

console.log(CountryOptions1);

Looking at your code, you expect this to be an array of objects. What is it in reality? If it is not what you expect, see if you can figure out why.

i understand whats the message of the error page means , my question was why is it undefined ? , that means the data havent been fetched when the rendering reach that point , but when i comment out this part till the end of the code and console.log the data suppose to be fetched , i see no problems with it and its working normaly .
thats im guessing that the CountryOptions1[0] part and the following code is rendered before the ffething is invoked becuase its places in useEffect function , thats why its end up undefined , thats my guess , and if its true hopw i can over come it.

If you want to learn how to be a computer programmer, you have to learn how to figure these things out. You don’t learn to be an auto mechanic by telling people to fix your car for you.

I told you what I would do to diagnose this. You need to learn how to do that. Spend some time, see if you can figure it out. If you can’t figure that out and don’t even understand what is happening, then I might suggest that you could consider going back and working on some fundamentals. But at the very least, spend some time trying to diagnose this before asking people, “fix it for me”.

2 Likes

And just to be clear, I’m not saying “if you can’t figure this out, then you don’t have what it takes to be a computer programmer”. I’m saying that struggling with this for a while will make you a better computer programmer. Even if you end up needing help, that is fine - you will learn from the effort.

Give it a shot. See if you can figure it out. Figure out why that thing is undefined. Keep tracing backwards until you find where it stops doing what you expect, and that’s probably your problem. At least figure that out.

As a professional developer, I probably spend almost as much time trouble shooting and debugging as I do writing code. This is an important skill. I know we all have this image of a computer programmer sitting at a computer, gleefully typing away, whistling along with the blue bird sitting on his shoulder, with the smell of fresh baked cookies wafting through the air… Yeah, sometimes that happens. But also a lot of time you’re pulling your hair out beating your head against the desk, screaming internally, “Why doesn’t this work?!? This should work!!!” And then you start to wonder if it’s too late to go to barber college. “Barbers don’t have to worry about this. They don’t stay up until 2am trying to fix a problem. They don’t have to constantly read documentation. Maybe it’s not too late to go to barber college.”

2 Likes

Welcome to the world of professional developers, identifiable by the dark circles around our eyes and bald patches from all-night hair-pullers.

Let me second this. We don’t learn to be great at coding by being great at coding. We learn to be great at coding by breaking stuff, writing ugly code, constructing crazy logic that, a year from now, will make us cringe.

Looking at your code, part of me has to wonder if this is being both overbought and underplanned. At a guess, you had an idea what your data looked like and a pretty good idea what you wanted three component to do, so you got busy coding. But when the data acted in an unplanned manner, your code kinda freaked, so you wrote something to cover that, but there are issues, so another patch…

I’ll tell you, from reading the code and the conversation, this is known as an XY problem. You’re asking “How do i X?” When the problem isn’t what you think it is…there’s a problem behind the problem.

This is not to discourage you, you’re doing great - but the way you’re going, you’re painting yourself into a corner.

Step back, stop coding, and outline what it is you’re trying to accomplish. Without code, in simple words, block out how you’d like this to process.

2 Likes

@snowmonkey @kevinSmith Yeah you are 100% , and appreciate your valuable advice , as a new comer with no experience it can be frustrating , and yes my code is not 100% clean and professional but it will get better with practice and im confident that im eventually gonna get there , my intention was not to get solutions on a silver plate , i was hoping if someone just gave me an indirect hint to what was probably going wrong then i can take it from there . i believe that why we are here is to learn from each other :slight_smile: , appreciate the advice :slight_smile:

An indirect hint… How about this: the reason the framework is named React is, that is what it does. It is designed to react to events or changes in datastate. When state is updated, we can set certain responses to that, but react itself may also update displays to sync with data.

Within your QuestionsList, you define some state variables, but are those in any way directly related to the displayed data?

The undefined warning you are getting happens because, when your component loads, countries is set to [] and you call ,getOptions() synchronously (following the code step-by-step) rather than asynchronously (run the code when countries has changed).

Do you think Questions might be stateful data? Should React be aware of it as such, and react to it updating? Can you think of another useEffect that might help, maybe depending on some other state changes, to update the Questions state?

i could use useEffect for the getOptions() function with the countries as a dependency , giving that i’ve to invoke the function 5 times to get 5 arays for the variables CountryOptions1 , CountryOptions2 , CountryOptions3 , CountryOptions4 , CountryOptions5 , im not sure how i will be able to construct that , i tried to have 5 useEffects for each one of them but still wasnt sucessful in doing doing so .

Have you put this in a github repo yet? Seeing it in context might help.

Okay, so I copied yours and did a bit of playing. Getting it working required four changes:

  1. Adding a useState (questions, setQuestions), in order to keep the list of questions as a state variable.
  2. Adding a useEffect with two dependencies: countries and setQuestions. In that, we can create the questions array directly, without the gymnastics that, frankly, were hurting my brain. (2a, remove all the current synchronous code to create QuestionsList)
  3. Passing the questions to `.
  4. Fixing a typo in the QuestionOption.js file. :wink:

So 1 is required, because we want the app to re-render the game when the questions has been updated. In order for React to see this, it has to be a state variable, so it knows that it should render.

And in 2, there are some caveats: in order to avoid an error when the thing initially loads, you want to return early if countries has a zero-length. When the app first loads and countries===[], the useEffect that runs setQuestions should be bypassed, as there are no countries. Other than that, in the useEffect, you can simply build the questions structure directly. As to how, that’s a whole 'nother conversation.

Points 3 and 4 are clean-up - as we’re using questions rather than QuestionsList (variables should start lower case, by convention in React, components start with a capital letter), we need to wire questions in where you’ve loaded QuestionsList, and check your capitalization in QuestionOption.js.

2 Likes

thank you very much , bit of clarification , I with the cconcept that setting questions should be a state variable , but my questions structure is very complex as its an array of 5 objects with another nested array of options and other property for the flag , im not sure how it can be done with one useState , i started trying doing it with 5 useStates with every one state extract 5 randomly 5 countries inside of a useEffect with countries as a dependency , then i destruct the names and the flag images from each variable , was that your approach ?

The quote? Was my approach. I looked at your final output shape, an array of five objects, and the shape of each object. I planned how best to create the object, and then fill it. Only when i had an idea in place did i start coding.

Within the useEffect, i create an empty variable to use with setQuestions at the end. Then i looped five times, creating five objects in that variable. Each of those objects contain the image and the right answer.

Then i mapped over those five incomplete objects, and within the map i populated the rest of my options for the current object.

Finally, i setQuestions with that temporary variable. That variable holding the array of questions? The only variable i needed.

Not saying the only way, but i started with planning and tried to keep my code readable and self explanatory.

1 Like

When you get yours done, I’ll push mine to a repo and we can compare approaches. Looking forward to seeing it!

1 Like