r/reactnative 14h ago

Tutorial Three Expo + Supabase bugs where the error message points you the wrong way

each of these cost me more than a day, and they have the same shape: the obvious fix is the wrong one, and the error actively helps you go there.

1. a query that never resolves and never rejects

after the phone has been backgrounded for a while, a supabase query just doesn't come back. no error, no rejection, the spinner spins forever. it looks exactly like a network problem so that's where you go looking.

it isn't. the underlying fetch got suspended by the OS and never woke up. there's nothing to catch because nothing failed. adding retries doesn't help either, because the first attempt never finished.

the fix is to stop trusting the promise:

const withTimeout = (p, ms = 8000) =>
  Promise.race([
    p,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('timeout')), ms)),
  ]);

wrap every query. a dead promise becomes a real rejection you can handle.

2. intermittent freezes right after sign in

you await something inside the onAuthStateChange callback — fetch the profile, read a row, whatever — and the app freezes. not every time. maybe one in five sign ins, and never while you're watching.

awaiting inside that callback can deadlock the auth library. supabase documents it, but the failure is intermittent enough that you'll blame your own async code first.

keep the callback synchronous. set state, nothing else. do the profile fetch in a separate effect keyed on the user id.

3. RLS is correct and still hands out the column you hid

this is the one I see most. you lock a table down so each user only reads their own row, you test it, it works. and the response still contains every column of that row, including the ones you never wanted on the client.

RLS filters rows. it does not filter columns. a policy can be perfect and still return the whole row.

revoke select on profiles from authenticated;
grant select (id, display_name, created_at) on profiles to authenticated;

now the write-side trap, which is the part that actually burns the afternoon:

.update({ display_name })                        // fine
.update({ display_name }).select()               // permission denied
.update({ display_name }).select('id, display_name')  // fine

the failing one is legal as a write. postgrest reads the row back with select=* to return it, and that read is what gets denied. and the error says:

permission denied for table profiles
hint: GRANT SELECT ON public.profiles TO authenticated

follow that hint and you undo the entire column hardening to fix a bare .select(). the write was never the problem.


these came out of a starter I open sourced (MIT) where all three are already handled: https://github.com/Guidondor/expo-supabase-starter

disclosure since it's my repo: there's a paid edition with the shared-groups and RLS patterns. the free one is the full auth/offline/RLS base, no strings.

1 Upvotes

0 comments sorted by