#Save fetch in useState constant & Double render

1 messages · Page 1 of 1 (latest)

granite mountain
#

Hi guys, I can't get to find how I should do the fetch so when the component is rendered the result of the fetch is saved in a state constant.

Basically the problem in the code is that nothing appears on page because when the component renders, the fetch still haven't finished.

And second, as you can see in the images, the component looks like it's being rendered twice.

Thank you for reading! 🙂

repo: https://github.com/Shake998/quizzical-game

import { useState, useEffect } from "react";
import Question from './Question'
import '../css/Quiz.css'

function Quiz() {
    const [questions, setQuestions] = useState([])

    const renderQuestions = questions.map((question, index) => <Question key={index} question={question.question} />)

    useEffect(() => {
        fetch("https://opentdb.com/api.php?amount=5&category=9&difficulty=easy&type=multiple")
        .then(res => res.json())
        .then(data => console.log(data))
        console.log("Q" + questions)
    }, [])
    
    return (
        <div className="quiz">
            {renderQuestions}
        </div>
    )
}

export default Quiz;
GitHub

Section 4 Final Project - Scrimba React Course. Contribute to Shake998/quizzical-game development by creating an account on GitHub.

crimson root
#

To save data in state use method provided by the useState() which is setState()

crimson root
#

To stop first render you could do one thing and it will be not wise solution i think.

#

You could use useRef() with starting value false. Then wrap your fetch logic in "if...else", where if condition will be refValue == true then fetch data. In else change the refValue = true.

#
const a = useRef(false)

 useEffect(() => {
    if (a.current) {
      ....fetch logic & setting state
    } else {
      a.current = true;
    }
  }, [])```
granite mountain
#

I need the fetch to work before the render of the questions.

granite mountain
#

Update of the code:

#

If I uncomment the setQuestions() line then the app crashes

crimson root
#

To fetch data before component rendering, you must use another hook called useQuery(), which is designed specifically for api calls. and it begins execution concurrently with component rendering.

granite mountain
#

Thanks! I will check when I get home 😍

mighty gazelle
#

Just adding to what @crimson root said. Should you be using React Query? Yes, without a doubt. But just like other concepts in React, you should learn how to do it in base React first, then learn React Query.

#

By the way, I recommend “react-query” over “tanstack-query” at least for now. React Query represents v3 of the same software, and I find it to be far more stable than the updated v4 which they now call tanstack-query

#

I do a ton of API work, and the first thing I always recommend is to do a hard review of your actual response data. Lots of responses wrap their entire dataset inside of a “data” wrapper, which may leave you to do “questions.data.map” (PS: react query also automatically wraps it again in another data, which may leave you doing questions.data.data.map — I’ve seen questions.data.data.results.map before as well — it can get quite a bit silly sometimes!!)

#

The thing is, I’m pretty familiar with this problem in both vanilla react and react query, it doesn’t magically get solved by using react query. The solution here isn’t to wait to launch the effect based on some circumstance, it’s actually to wait to do the map, and there is a really easy way to do this by using a “?” operator on the map. So let’s say it’s questions.data.map for example, add a ? after data so that it reads “questions.data?.map”. This tells JavaScript to only do what it’s about to do if the data actually exists. If not, skip and come back to it. Once you’ve figured out how to do it in vanilla react, jump into react query because honestly it’s super nice and I wouldn’t personally go back to doing it any other way. Hope this helped!!

crimson root
granite mountain
#

@mighty gazelle send me your paypal, I want to invite you to a coffe hahaha
You teached me more than my classic studies professor.

mighty gazelle
#

Hahaha, thank you I appreciate it, but thats not necessary! This community has given a lot to me and its nice to give back in my little areas of expertise. Let me know if you have any further questions and I'm happy to help. Cheers!

granite mountain
#
const [questions, setQuestions] = useState([])
    const [quizChecked, setQuizChecked] = useState(false)
    const [score, setScore] = useState(0)

    useEffect(function() {
        console.log("Fetched data")
        const data = fetch("https://opentdb.com/api.php?amount=5&category=9&difficulty=easy&type=multiple")
            .then(res => res.json())
            .then(data => console.log(data))
        setQuestions(data.results)
    }, [])
#

When I do setQuestions is when questions becomes an undefined value

#

console.log(data) prints this

#

This is how I show the questions in the page

#

But there's no questions visible

mighty gazelle
#

Based on the way you're doing it, questions shouldn't be undefined, that is really odd.

granite mountain
mighty gazelle
#

You initialize state before you call questions in the map, right?

granite mountain
#

yes

#

I have a repo if you want btw

mighty gazelle
#

please!

granite mountain
granite mountain
granite mountain
#

I just updated the master, because I forgot to merge the api branch to the master

#

Ij ust merged it

mighty gazelle
#

no worries. Okay, I've fixed your code, but I honestly think it'd be better if you figured it out. I'd be doing you a disservice if I just pushed the fixed code to your repo and went about my day. I'll get you on the right track and if you end up feeling like putting your fist through a wall or breaking your monitor, I'll lead you through my solution. You seem like a cool guy and I genuinely want you to get better so please don't think I'm insulting you, I'm not! This feedback is all constructive and written with love.

The first thing I did was clean up your code -- to be honest its a bit messy. You know when you clean your desk/setup up and things start to make more sense? I would start by cleaning up your code. It'll allow you to really look and figure things out in a much more simple way.

Some of your implementations are also a bit interesting. For example, you run the function at the top and then export it at the bottom. I don't think this is a serious issue but typically react components are structured with the whole thing all in one line at the top

return(
//JSX
)
}

I'd really take a hard look at your code and think to yourself "Am I doing this in a way that is just a bit funky? Are any of my implementations a little silly?". Another example is how you use the playAgain function. To be honest, my solution involved scrapping that function in favor of something else. That whole interaction seemed super funky to me.

The solution is actually pretty simple, I was struggling with it for awhile, and kinda facepalmed when I realized -- it was right in front of our face! Hint: it has to do with fetch and how chained promises work. (Another hint: I wouldn't have been able to see it if I didn't do some serious janitorial work first -- I couldn't find the "bug" with all the clutter!). As many programming projects go, from novice to expert, its 1 line causing you issues.

The second part of the solution is making sure you're not mapping over an array that doesn't exist. The error "... is not an object" is caused by react going "hey man! you told me I was supposed to be mapping over an array, and there is no array here! what the heck!". A bit of a hint there: use the ? operator like how I explained before.

Good luck! I know you can do it 🙂

granite mountain
#

Damn thanks Mason!
I'm taking the challenge and I will try to fix it.
Thanks for all the work, I'll let you know.

mighty gazelle
#

let me know how it goes! 🙂 I'll be here around if anything else arises

granite mountain
#

Sure!

harsh arch
#

Also, using ? when displaying something that might be null isn’t a good idea, it’s the simplest solution for you and the ugliest for the user.

#

Apart from that I’ve had 0 problems with using tanstack query over v3

#

And no tanstack-query isn’t for api calls only it’s anything with a promise

#

And your whole “typically react components” thing about doing a default export? Nonsense, people do whatever they want, no difference where you export

#

Another thing is don’t use index in a map to set the key in a list

granite mountain
#

@mighty gazelle I'm desperate with the fetch 6680_this_is_fine

#

||I know the error is in the setQuestions() line but I don't understand why, and every example I find on internet it says taht's how it should be working||

#

||If I print the value I'm trying to assign to the setQuestions() it logs the array correctly, but If I try to assign it to the questions state it just breaks||

#

Okay that was a real mental breackdown, and literally 5 seconds later I fixed something

#

I'm gonna continue Im sorry haha

granite mountain
#

So now the fetch is working and I know I can fix the second part easily but before moving forward I would like to know if you thing I'm doing good with the way I do the options array or If I should do it another way

crimson root
#

@granite mountain Why do you use hard coding? If you hard code everything in the playAgain function, what is the point of fetching data from the API?

granite mountain
#

In the fetch-api branch of the repo, I just fixed the fetch so I deleted all the hardcoded questions

crimson root
#

push updated repo

granite mountain
#

done

granite mountain
#

Okay I made it work

#

But with terrible performance

#

Updated on master

#

And I tried to comment the code

steep arrow
# granite mountain Hi guys, I can't get to find how I should do the fetch so when the component is ...

the main reason why you have double fetch, because react in strict mode will mount, unmount and mounts your component, just to make sure the state is pure. This will not happen in production.

@crimson root had a good idea regarding the useRef solution, and it is not unwise to do it imo, it can be a solution. It is a common solution for these scenarios when it happens in production, but if you really have multiple calls for some reason in production, you might also consider checking your dependency array or set up a base condition in your useEffect to prevent multiple calls.

Another solution you can do is to use AbortController and send the signal to your fetch, then abort the request if you fire multiple ones. Cancelled requests will still be noticeable in the network tab.

React-query is amazing, it gives you brilliant control over your data but I absolutely agree with @mighty gazelle , you should learn the very basics first so you have a solid understanding about how everything works at "lower level" then you can introduce any library you wish to make your life easier if needed.

BUT even if you use React-query, you would still have double request calls, it won't solve your current issue.

#

Regarding UX, I agree with @harsh arch , you should use conditional rendering in your component to show the user different state of the application. eg.:

interface User {
  id: string
  name: string
  age: string
}

const Status = {
  Idle: 'idle',
  Pending: 'pending',
  Rejected: 'rejected',
  Resolved: 'resolved',
}

const MyComp = () => {
  const [status, setStatus] = useState(Status.Idle)
  const [error, setError] = useState<null | Error>(null)
  const [users, setUsers] = useState<null | User[]>(null)

  useEffect(() => {
    setStatus(Status.Pending)
    getUsers()
      .then(resp => resp.json())
      .then(users => {
        setStatus(Status.Resolved)
        setUsers(users)
      })
      .catch(error => {
        setStatus(Status.Rejected)
        setError(error.message)
      })
  }, [])


  if (status === Status.Pending) {
    return <div>Loading...</div>
  }
  
  if (status === Status.Rejected && error) {
    return <div>Ops, something went wrong: {error}</div>
  }

  if (status === Status.Resolved && users.length === 0) {
  return <div>No data found</div>
} 

  return (
    <div>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            <p>{user.name}</p>
            <p>{user.age}</p>
          </li>
        ))}
      <ul>
    </div>
  )
}
granite mountain
granite mountain
steep arrow
# granite mountain Thanks for explaining and giving your opinion on react-query, useRef and so!

me pleasure.

Don't take me wrong, I love react-query and in fact I am using it in our internal admin site but I've just recently implemented it into our project, because the other library I was using become dead. 😅 (it was a very big refactor 🫥 )
React-query also provided me with cache control which also helps to improve UX on certain screens, but in the very beginning when I started the whole project, I was using fetch or my own implementation of useAsync then a library called use-async.

Eventually when your application get big enough, you will be "forced" or "tempted" to use some of these libraries which is absolutely fine.

Supplemental: If you want something very basic and easy to use, you could also take a look at SWR: https://swr.vercel.app/docs/getting-started

steep arrow
granite mountain
mighty gazelle
#

Okay, I see you're still a bit confused. Could you share with me what you have via the github repo?

#

Also Barry I know some of what I said wasn't 100% accurate, and maybe a bit nitpicky but the point is to help someone understand. If I were to say that react-query was a server state management tool and could be used for anything that returns a promise, I'd be doing more harm than good.

Its a tool that can help you make API calls --> learn more about it, learn exactly what it is and what its exact job is.

granite mountain
#

The repo is updated and I haven't made any canges since last push to react-query branch