avoid rendering more hooks: running trpc query based on supabase session's user

i have a router workspace with query getWorkspace that takes the current supabase session's user's UUID id and returns details of the workspace they're a member of
export const workspaceRouter = createTRPCRouter({
getWorkspace: publicProcedure
.input(
z.object({
user_id: z.string(),
}),
)
.query(async ({ input, ctx }) => {
const prisma = new PrismaClient()
try {
await prisma.$connect()
const { user_id } = input

const workspace = await ctx.prisma.company.findFirst({
where: {
members: {
has: user_id,
},
},
})

return workspace
} finally {
await prisma.$disconnect()
}
}),
})
export const workspaceRouter = createTRPCRouter({
getWorkspace: publicProcedure
.input(
z.object({
user_id: z.string(),
}),
)
.query(async ({ input, ctx }) => {
const prisma = new PrismaClient()
try {
await prisma.$connect()
const { user_id } = input

const workspace = await ctx.prisma.company.findFirst({
where: {
members: {
has: user_id,
},
},
})

return workspace
} finally {
await prisma.$disconnect()
}
}),
})
but on my /pages/today.tsx where i need that workspace info, it's throwing Unhandled Runtime Error Error: Rendered more hooks than during the previous render
import { type NextPage } from 'next'
import { api } from '~/utils/api'
import { DateTime } from 'luxon'
import { useEffect, useState } from 'react'
const Today: NextPage = () => {
const [user, setUser] = useState<User>()
useEffect(() => {
const getUser = async () => {
const {
data: { session },
} = await supabase.auth.getSession()

if (!session) {
console.log('no session')
} else if (!session.user) {
console.log('no user')
} else {
console.log('session.user: ', session.user)
setUser(session.user)
}
}
void getUser()
}, [])

if (typeof user !== 'undefined') {
const temp_workspace = api.workspace.getWorkspace.useQuery({
user_id: user.id,
})
console.log('temp: ', temp_workspace)
}
}

export default Today
import { type NextPage } from 'next'
import { api } from '~/utils/api'
import { DateTime } from 'luxon'
import { useEffect, useState } from 'react'
const Today: NextPage = () => {
const [user, setUser] = useState<User>()
useEffect(() => {
const getUser = async () => {
const {
data: { session },
} = await supabase.auth.getSession()

if (!session) {
console.log('no session')
} else if (!session.user) {
console.log('no user')
} else {
console.log('session.user: ', session.user)
setUser(session.user)
}
}
void getUser()
}, [])

if (typeof user !== 'undefined') {
const temp_workspace = api.workspace.getWorkspace.useQuery({
user_id: user.id,
})
console.log('temp: ', temp_workspace)
}
}

export default Today
22 Replies
barry
barry•2y ago
Instead, set disabled to true or false Don't do hooks optionally, never.
Domcario
DomcarioOP•2y ago
sorry, what should i be disabling?
barry
barry•2y ago
useQuery hook takes options, one of those are called disabled, takes in true or false Oh wait no it's called enabled lol
Domcario
DomcarioOP•2y ago
aight, i modified it to this but since supabase's type im importing for User can also be undefined, it's giving me that message. which is why i had the condition before. is there a better way to handle this?
// if (typeof user !== 'undefined') {
const temp_workspace = api.workspace.getWorkspace.useQuery(
{
user_id: user.id,
},
{ enabled: false },
)
console.log('temp: ', temp_workspace)
// }
// if (typeof user !== 'undefined') {
const temp_workspace = api.workspace.getWorkspace.useQuery(
{
user_id: user.id,
},
{ enabled: false },
)
console.log('temp: ', temp_workspace)
// }
barry
barry•2y ago
const temp_workspace = api.workspace.getWorkspace.useQuery(
{
user_id: user.id,
},
{ enabled: user.id !== undefined },
)
const temp_workspace = api.workspace.getWorkspace.useQuery(
{
user_id: user.id,
},
{ enabled: user.id !== undefined },
)
Idk how Supabase's promises work but I'm assuming this would work Or asserting that typeof user.id === string should work too
Domcario
DomcarioOP•2y ago
hmm that won't get around the typeerror, since the user state is undefined until the getUser runs in useEffect
barry
barry•2y ago
NOT equal undefined The not is important
Domcario
DomcarioOP•2y ago
whoops i forgot to change it back
barry
barry•2y ago
What if you did this enabled: user && "id" in user
Domcario
DomcarioOP•2y ago
same issue, does order matter for useQuery's args or something? nvm def not order
barry
barry•2y ago
I guess you can just do optional chaining
Domcario
DomcarioOP•2y ago
how would i go back to that and avoid the error we started with 😦 Unhandled Runtime Error Error: Rendered more hooks than during the previous render.
barry
barry•2y ago
Just do user?.id ?
Domcario
DomcarioOP•2y ago
oh i misunderstood, let me try that that means i'll have to allow user_id: z.union([z.string(), z.undefined()]), in the trpc router's input right
barry
barry•2y ago
Don't think so
Domcario
DomcarioOP•2y ago
mainly because of this
mrnicericee
mrnicericee•2y ago
you're also using a useEffect remove getUser from the useEffect, and if you really want to, put it on a useCallBack the useEffect probably is the one that's running too many times
Domcario
DomcarioOP•2y ago
just clarifying, remove getUser's implementation and leave the call in the useEffect? or remove the useEffect and its contents all together?
mrnicericee
mrnicericee•2y ago
from the docs it looks like you're trying to redo the supabase useUser hook? source: https://supabase.com/docs/guides/auth/auth-helpers/nextjs
import { Auth } from '@supabase/auth-ui-react'
import { ThemeSupa } from '@supabase/auth-ui-shared'
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
import { useEffect, useState } from 'react'

const LoginPage = () => {
const supabaseClient = useSupabaseClient()
const user = useUser()
const [data, setData] = useState()

useEffect(() => {
async function loadData() {
const { data } = await supabaseClient.from('test').select('*')
setData(data)
}
// Only run query once user is logged in.
if (user) loadData()
}, [user])

if (!user)
return (
<Auth
redirectTo="http://localhost:3000/"
appearance={{ theme: ThemeSupa }}
supabaseClient={supabaseClient}
providers={['google', 'github']}
socialLayout="horizontal"
/>
)

return (
<>
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
<p>user:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
)
}

export default LoginPage
import { Auth } from '@supabase/auth-ui-react'
import { ThemeSupa } from '@supabase/auth-ui-shared'
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
import { useEffect, useState } from 'react'

const LoginPage = () => {
const supabaseClient = useSupabaseClient()
const user = useUser()
const [data, setData] = useState()

useEffect(() => {
async function loadData() {
const { data } = await supabaseClient.from('test').select('*')
setData(data)
}
// Only run query once user is logged in.
if (user) loadData()
}, [user])

if (!user)
return (
<Auth
redirectTo="http://localhost:3000/"
appearance={{ theme: ThemeSupa }}
supabaseClient={supabaseClient}
providers={['google', 'github']}
socialLayout="horizontal"
/>
)

return (
<>
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
<p>user:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
)
}

export default LoginPage
Supabase Auth with Next.js | Supabase Docs
Authentication helpers for Next.js API routes, middleware, and SSR.
mrnicericee
mrnicericee•2y ago
import { type NextPage } from 'next'
import { api } from '~/utils/api'
import { DateTime } from 'luxon'
import { useUser } from '@supabase/auth-helpers-react'
import { useEffect, useState } from 'react'
const Today: NextPage = () => {
const user = useUser() // use the supabase hook to check if there is a user
const temp_workspace = api.workspace.getWorkspace.useQuery({
user_id: user.id,
}, {
enabled: !!user, // won't run unless there is a user
})
}

export default Today
import { type NextPage } from 'next'
import { api } from '~/utils/api'
import { DateTime } from 'luxon'
import { useUser } from '@supabase/auth-helpers-react'
import { useEffect, useState } from 'react'
const Today: NextPage = () => {
const user = useUser() // use the supabase hook to check if there is a user
const temp_workspace = api.workspace.getWorkspace.useQuery({
user_id: user.id,
}, {
enabled: !!user, // won't run unless there is a user
})
}

export default Today
it could get reduced to that
Domcario
DomcarioOP•2y ago
appreciate this! been trying to debug this but
const { user } = useUser()
const { user } = useUser()
causes Unhandled Runtime Error TypeError: Cannot destructure property 'user' of '(0 , _supabase_auth_helpers_reactWEBPACK_IMPORTED_MODULE_1.useUser)(...)' as it is null. and if i just do
console.log('useUser: ', useUser())
console.log('useUser: ', useUser())
, i'll get useUser: null in console and as a failsafe, i brought back the useEffect with getUser() and it would correctly show my user info since im signed in /src/pages/today.tsx
import { type NextPage } from 'next'
import { useUser } from '@supabase/auth-helpers-react'

const Today: NextPage = () => {

// const { user } = useUser()
// console.log('user: ', user)
console.log('useUser: ', useUser())

return (
<div>
<h1>Today</h1>
</div>
)
}

export default Today
import { type NextPage } from 'next'
import { useUser } from '@supabase/auth-helpers-react'

const Today: NextPage = () => {

// const { user } = useUser()
// console.log('user: ', user)
console.log('useUser: ', useUser())

return (
<div>
<h1>Today</h1>
</div>
)
}

export default Today
and src/pages/_app.tsx
import { useState } from 'react'
import type { AppProps, AppType } from 'next/app'
import { api } from '~/utils/api'
import '~/styles/globals.css'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import {
SessionContextProvider,
type Session,
} from '@supabase/auth-helpers-react'
import { env } from '~/env.mjs'

// const MyApp: AppType = ({ Component, pageProps }) => {
function MyApp({
Component,
pageProps,
}: AppProps<{
initialSession: Session
}>) {
const [supabaseClient] = useState(() =>
createBrowserSupabaseClient({
supabaseUrl: env.NEXT_PUBLIC_PREVIEW_SUPABASE_URL,
supabaseKey: env.NEXT_PUBLIC_PREVIEW_SUPABASE_ANON_KEY,
}),
)
return (
<SessionContextProvider
supabaseClient={supabaseClient}
initialSession={pageProps.initialSession}
>
<Component {...pageProps} />
</SessionContextProvider>
)
}

export default api.withTRPC(MyApp)
import { useState } from 'react'
import type { AppProps, AppType } from 'next/app'
import { api } from '~/utils/api'
import '~/styles/globals.css'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import {
SessionContextProvider,
type Session,
} from '@supabase/auth-helpers-react'
import { env } from '~/env.mjs'

// const MyApp: AppType = ({ Component, pageProps }) => {
function MyApp({
Component,
pageProps,
}: AppProps<{
initialSession: Session
}>) {
const [supabaseClient] = useState(() =>
createBrowserSupabaseClient({
supabaseUrl: env.NEXT_PUBLIC_PREVIEW_SUPABASE_URL,
supabaseKey: env.NEXT_PUBLIC_PREVIEW_SUPABASE_ANON_KEY,
}),
)
return (
<SessionContextProvider
supabaseClient={supabaseClient}
initialSession={pageProps.initialSession}
>
<Component {...pageProps} />
</SessionContextProvider>
)
}

export default api.withTRPC(MyApp)
barry
barry•2y ago
You know you can just call create client outside of a component No usestate hacks
Want results from more Discord servers?
Add your server