Prevent possibly undefined user with useQuery

I have a hook useUser that looks basically like this
return useQuery(// fetch user)
return useQuery(// fetch user)
Lets imagine that its used in a Layout so when the user is loading the whole app is just a loading spinner. Now I want to get the user inside a component nested in Layout Using useUser hook makes no sense here because its return is always User | undefined as typescript doesn't know the user is loaded somewhere higher. How do I solve this problem? Checking
if(!user) return
if(!user) return
Doesn't seem good because the user is always here if the component rendered.
70 Replies
stanisław
stanisławOP2y ago
Also prop drilling user is not what I want to do here. Maybe I should?
cje
cje2y ago
returning null or whatever if the user isn't loaded is fine imo you're using react-query or trpc right?
stanisław
stanisławOP2y ago
react-query
cje
cje2y ago
even if you get the user further up in the in the component tree, the query might become stale or invalidated so you don't know for sure that it exists in the component
stanisław
stanisławOP2y ago
But if the query is stale or invalidated it is still there isnt it?
cje
cje2y ago
its possible to reach a state in react-query where the data is gone you could stick the user into context or zustand or something, tkdodo wrote some stuff about this recently
stanisław
stanisławOP2y ago
I was thinking about something likes this
const useSafeUser = () => {
const {data: user} = useUser()
if(!user) {
// throw error
}
return user
}
const useSafeUser = () => {
const {data: user} = useUser()
if(!user) {
// throw error
}
return user
}
And this hook would be used inside a Layout. So when I used this hook somewhere where the user wasnt fetched before i would immediately see an error This way I wouldnt have to do this check
if(!user) return null
if(!user) return null
Idk if its a good idea tho
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
stanisław
stanisławOP2y ago
😎 ty
robotkutya
robotkutya2y ago
https://tanstack.com/query/v4/docs/typescript#type-narrowing
const Layout = () => {
const {data: user, isLoading, isSuccess} = useUser()

if (isLoading) {
return <Loading />
}

if (isSuccess) {
return <User user={user} />
}

return <Error />
}
const Layout = () => {
const {data: user, isLoading, isSuccess} = useUser()

if (isLoading) {
return <Loading />
}

if (isSuccess) {
return <User user={user} />
}

return <Error />
}
This is what you want, no?
TypeScript | TanStack Query Docs
React Query is now written in TypeScript to make sure the library and your projects are type-safe! Things to keep in mind:
stanisław
stanisławOP2y ago
I dont want to prop drill it as you do it here
return <User user={user} />
return <User user={user} />
I would like to use my useUser hook further in the component tree but not check everytime if its loaded because It will be always there in for example <User /> I described it above the thread
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
stanisław
stanisławOP2y ago
@Moro Why do you think 2nd custom hook that always returns user otherwise throws breaks typesafety? I can't think of a case when it would break something in production. Such a mistake would be easily noticeable during development
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
stanisław
stanisławOP2y ago
sure thanks
robotkutya
robotkutya2y ago
you are kind of reinventing the wheel the purpose of react query is to handle these issues for you and act as a global state for your application while handling all the complications that synchronizing with server state inherently has and providing a clean, easy to use API for you and your components this is not possible you have to check if you do it like this, then it's probably better to put it in a context
stanisław
stanisławOP2y ago
nah ur just misunderstanding us @robotkutya
robotkutya
robotkutya2y ago
oh, okay then so if for some reason you don't want to just use react query, and check for isSuccess as recommentded by the docs (and common sense) and want a custom hook the way you described you might as well just put it into a context so the error handling is cleaner you won't be able to call the custom hook, unless it is inside the provider, that has the user as defined
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
which is why it would make sense to put it into a context if you don't want the prop drilling, so the entire component tree after the initial loading state has access to a not undefined user object having it in a context helps with components that rely on the user not being used outside the context
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
in react query's context but OP doesn't like the fact that the return type of that is foo | undefined and also doesn't like the recommended way of narrowing the type via checking for {isSuccess} = useFoo()
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
so, what you can do, is create a context, that needs a defined user <UserContext /> and then handle the loading before setting up the context which is what happens anyways
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
hold my tea... brb...
# User Context and Provider

const UserContext = createContext<User | undefined>(undefined)

type ProviderProps = {
children: React.ReactNode
user: User
}

const UserProvider = ({
children,
user
}: ProviderProps) => {
// you could memoize this and/or add functions related to the user object
const value = user

return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
)
}

function useUser() {
const context = useContext(UserContext)
if (context === undefined) {
throw new Error('useUser must be used within a UserProvider')
}
return context
}

export { UserProvider, useUser }
# User Context and Provider

const UserContext = createContext<User | undefined>(undefined)

type ProviderProps = {
children: React.ReactNode
user: User
}

const UserProvider = ({
children,
user
}: ProviderProps) => {
// you could memoize this and/or add functions related to the user object
const value = user

return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
)
}

function useUser() {
const context = useContext(UserContext)
if (context === undefined) {
throw new Error('useUser must be used within a UserProvider')
}
return context
}

export { UserProvider, useUser }
there you go
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
I think it makes sense to make it explicit that the component requires a user that is defined better error handling but, I wouldn't do either of these... since react query already does this well... and you can just check for {isSuccess} on the page load / layout / data fetching level and just pass down the prop to the presentational / pure components *parts of the user they need not the whole thing
stanisław
stanisławOP2y ago
But the error handling is the same here Both ways u get an error when you try to access user when its not fetched
robotkutya
robotkutya2y ago
not entirely if you use a component that has the custom hook in it with the provider - context version you get immediate feedback in your editor red squigly lines
stanisław
stanisławOP2y ago
🤔
robotkutya
robotkutya2y ago
prevents you from breaking the app, when you accidentally pull in the component outside the loading state
stanisław
stanisławOP2y ago
how Editor doesnt recognize if the context is used within provider The error would be noticeable in the runtime
robotkutya
robotkutya2y ago
maybe I'm trippin' let me check
stanisław
stanisławOP2y ago
const context = useContext(UserContext)
if (context === undefined) {
throw new Error('useUser must be used within a UserProvider')
}
const context = useContext(UserContext)
if (context === undefined) {
throw new Error('useUser must be used within a UserProvider')
}
Thats exactly what u did above If context is undefined - its used outside a provider Then the error is thrown and u will see this error in runtime Not in IDE
robotkutya
robotkutya2y ago
yeah but doesn't this throw a TS error?
stanisław
stanisławOP2y ago
How would ts know if u r trying to access context inside/outside a provider Check it ;D
robotkutya
robotkutya2y ago
hold on
stanisław
stanisławOP2y ago
btw I would check that differently as you would probably initialize context as null rather than undefined
if(!context)
if(!context)
robotkutya
robotkutya2y ago
meh... I totally thought there is some TS type narrowing magic in the background and the IDE would pick it up okay, then the only advantage is (btw, it's a BAD solution I think) that it's more explicit that you need the user object defined if you pull in a component somewhere down the tree, and you are accidentally outside of the branch that handles the loading state anyway, I kind of just pulled that one out of my *ss, I still think checking for {isSuccess} coming from react-query is the way to go, and passing down the tree oh yeah, one more thing... doesnt putting it inside a context help with error boundaries? so if it's in a context, your app doesn't crash, if it's just a custom hook and you throw an error, the whole app crashes...?
stanisław
stanisławOP2y ago
I think that in both ways app crashes
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
I mean, it's your code and you can do whatever you want... But if you want to go full Ron Swanson "I know more than you" on TypeScript Might as well just start using the exclamation mark
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
yes, you are if you follow to what OP wants to do (no passing down as prop, etc) react-query has the type narrowing built in, safely and it is tied to the {isSuccess} check it's not an error when the user object is undefined it's the reality of having it in the cache or not having it in the cache
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
okay sure, if you consider that an improvement...
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
yeah, and you have 2 options 1 option makes sure at compile time that you don't accidentally go into a branch where the user is not available
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
option 2 can only tell at runtime i'm going to chose option 1
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
it's a pretty bad pattern but I guess we reached a point where we agree to disagree
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
yes, of course, but OP vehemently opposed passing the defined user object down as a prop or putting it in a context or a global state (after the check)
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
not really, if your components are more than 2 levels deep, you probably have some issues you should resolve plus, it's better to only pass the parts that you need so if a component only needs let's say 2 attributes off of user, don't pass the whole god damn thing down as a prop
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
2 levels of hierarchy A --> B --> C A passes it down to B passes it down to C I'm in general fine with prop drilling up until this if there is C --> D then I reach for a solution (like jotai or context, global state)
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
yaaay then we agree 😄 okay let's go find something to argue about
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
oh man, go check it out react-query solved the whole data fetching game it's over, done, finito
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
I had such a bad experience with apollo in general
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
urql Documentation
urql Documentation
A highly customisable and versatile GraphQL client.
robotkutya
robotkutya2y ago
have you tried this?
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
robotkutya
robotkutya2y ago
glhf 🖖
Want results from more Discord servers?
Add your server