skip to Main Content

I’m working on a T3 app using TS, React, Next.js, prisma and tRPC. I have a header in my app showing the number of credits a user has. To get the number of credits I make a call to an API which takes some time to return an answer. As I use the amount of credits as a state, this means that the page renders with credits undefined before the API returns e.g. 10.

The concept of it looks like this:

import { useEffect, useState } from 'react'
import { api } from '~/utils/api'

export function Header() {
  const nbrOfCredits = api.user.getCredits.useQuery()
  const [credits, setCredits] = useState(nbrOfCredits.data)

  return <>Credits remaining: {credits}</>
}

Credits is recognized as number | undefined.
My API is working fine, it just takes too long for the response for the state to be set.

I’ve tried a lot of stuff but I’m very new to webdev and not exactly an expert in either React, TS or JS.

I figured setting the value again in a useEffect on render might do the trick but it is still undefined. Once the page is loaded I can console log a value of nbrOfCredits so it does eventually show up.

I also tried creating a function to call on render using try, catch, finally but I never got that to work either:

async function handleCreditsRefresh() {
    try {
      setIsLoading(true)
      await nbrOfCredits
        .refetch()
        .then(() => {
          const cred =
            typeof nbrOfCredits === 'undefined' ? 0 : nbrOfCredits.data
          setCredits(cred)
        })
        .catch((err) => {
          setError(err)
        })
        .finally(() => setIsLoading(false))
    } catch (error) {
      console.log(error)
    }
  }

  useEffect(() => {
    void handleCreditsRefresh()
  }, [])

return (
          <>
            {isLoading ? (
                <>Credits remaining: </>
              ) : (
                <>Credits remaining: {credits}</>
              )}
         </>
)

2

Answers


  1. Chosen as BEST ANSWER

    I solved it in a good enough way I think with the input from knoxgon.

    const nbrOfCredits = api.user.getCredits.useQuery()
    
      const [credits, setCredits] = useState(nbrOfCredits.data)
      const [isLoading, setIsLoading] = useState(true)
    
      function handleCreditsRefresh() {
        if (nbrOfCredits.isLoading) {
          setIsLoading(true)
          setTimeout(handleCreditsRefresh, 200)
        } else {
          setCredits(nbrOfCredits.data)
          setIsLoading(false)
        }
      }
    

    and in the return:

    {isLoading ? (
                    <>Credits remaining: {nbrOfCredits.data}</>
                  ) : (
                    <>Credits remaining: {credits}</>
                  )}
    

    using nbrOfCredits.data, the engine waits for the API response before rendering the component, basically solving my problem. I have a button for refreshing the credits though so I need to keep the logic in handleCreditsRefresh.


  2. I see that you’re using useQuery which normally provides access to isLoading and data which you can leverage.

    What you need to do is wait for the request to be completed. You would use conditional rendering to ensure that data is only rendered when it’s available.

    export function Header() {
        const nbrOfCredits = api.user.getCredits.useQuery();
        
        //Here, you would need to render spinner/loading component
        //until data is ready
        if (nbrOfCredits.isLoading) {
            return <>Loading...</>;
    
        return <>Credits remaining: {nbrOfCredits.data}</>;
      }
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search