Skip to main content

Solid client

WIP

SolidJS provider + hooks - authUseSession, authUseSignIn, authUseSignOut. Built on createSignal so updates are fine-grained.

Setup

src/index.tsx
import { render } from 'solid-js/web'
import { AuthProvider } from '@gentleduck/auth/client/solid'
import App from './App'

render(
  () => (
    <AuthProvider baseUrl="/auth">
      <App />
    </AuthProvider>
  ),
  document.getElementById('root')!,
)

AuthProvider accepts the same options as authCreateClient plus an optional client prop if you want to share an existing vanilla client.

authUseSession

import { authUseSession } from '@gentleduck/auth/client/solid'

function App() {
  const { data, status } = authUseSession()

  return (
    <>
      {status() === 'loading' && <div>Loading...</div>}
      {status() === 'guest' && <SignInForm />}
      {status() === 'authed' && (
        <div>Hi, {data().identity?.profile?.displayName}</div>
      )}
    </>
  )
}

data and status are signals (call them to read). Updates are reactive and fine-grained so only the components that read them re-render.

authUseSignIn / authUseSignOut

import { authUseSignIn } from '@gentleduck/auth/client/solid'
import { createSignal } from 'solid-js'

function SignInForm() {
  const signIn = authUseSignIn()
  const [email, setEmail] = createSignal('')
  const [password, setPassword] = createSignal('')

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault()
        await signIn.mutate({
          providerId: 'password',
          input: { email: email(), password: password() },
        })
      }}
    >
      <input type="email" value={email()} onInput={(e) => setEmail(e.currentTarget.value)} required />
      <input type="password" value={password()} onInput={(e) => setPassword(e.currentTarget.value)} required />
      <button disabled={signIn.loading()}>
        {signIn.loading() ? 'Signing in...' : 'Sign in'}
      </button>
      {signIn.error() && <p class="text-red-500">{(signIn.error() as { code: string }).code}</p>}
    </form>
  )
}

The mutate / loading / error shape mirrors React's useMutation. loading and error are signals - read them as loading() and error().

SSR

AuthProvider accepts noInitialFetch so the server render does not call /session. Pass server-resolved state via initialState:

<AuthProvider
  baseUrl="/auth"
  noInitialFetch
  initialState={serverSession}
>
  <App />
</AuthProvider>

Errors

The error signal carries the raw thrown value. Match on .code for typed discrimination:

{signIn.error() && (() => {
  const e = signIn.error() as { code: string }
  if (e.code === 'AUTH/INVALID_CREDENTIALS') return <p>Wrong email or password</p>
  if (e.code === 'AUTH/MFA_REQUIRED') return <MfaPrompt />
  return <p>Something went wrong: {e.code}</p>
})()}

Reactivity across tabs

The Solid client subscribes to BroadcastChannel updates from the vanilla client. Signing in or out in one tab updates the authUseSession signal in every other tab automatically.