Firebase Hooksv0.3.0

How every hook works

One contract, so learning one hook is learning them all.

Every hook in this package follows the same shape. Once you've used one, the rest are predictable.

Every hook needs an Auth instance

Below an AuthProvider the hooks take its auth, so there's nothing to pass:

const { login } = useLogin();
const { signup } = useSignup({ sendVerificationEmail: false });

You can still pass one explicitly, and it wins over the provider's. That's what a second Firebase project needs, and it's the only option if you're not using a provider at all:

const { login } = useLogin(auth);
const { login } = useLogin(auth, { onIdToken: createSession });

auth comes from the firebase package, not this one — getAuth(initializeApp(config)), created once in your app and imported where you need it. Getting started has the file in full.

Passing null is always safe, and it never means "use the provider's". It means the instance isn't ready: state hooks report loading, and actions fail cleanly with { success: false, error, code, cause } rather than throwing. That distinction is what lets you hold a hook back while Firebase boots without it quietly running against the provider's instance instead.

const { login } = useLogin(auth); // auth may still be null here — fine

Every action resolves to a result

Actions never throw. Each resolves to a HookResult:

{ success: true, ...data }
// or
{ success: false, error, code, cause }

Alongside the result, every action hook reports where it is: status is "idle" | "pending" | "success" | "error", with isIdle, isPending, isSuccess and isError derived from it, error carrying the message for rendering, and reset() returning to idle — for a form the user retries, so a stale message doesn't sit under the new attempt. It's the same shape as TanStack Query's mutation result. Hooks that act on mount, like useVerifyEmail, report AsyncStatus instead: the same words without idle.

const result = await login(email, password);
if (result.success) router.push('/dashboard');
else if (result.code === 'auth/too-many-requests') startCooldown();

See Error handling for the full model.

Writing a wrapper around a hook

Everything a wrapper needs to name is exported. Each hook's options interface comes from the auth entry, so you can accept and forward them without restating the shape:

import {
  useLogin,
  type UseLoginOptions,
  type UseLoginResult,
} from '@timonwa/firebase-hooks/auth';

export function useAppLogin(options?: UseLoginOptions): UseLoginResult {
  const { login, isPending, error } = useLogin(options);
  // …your own state on top
  return { login, isPending, error };
}

Every hook exports what it returns as Use<Name>Result — UseLoginResult, UseSignupResult, and so on — so a wrapper can state its return type rather than re-deriving it. AuthProviderProps and UseAuthResult are exported too, for an app provider layered over this one — see Server sessions.

The result and error shapes — HookResult, HookErrorOptions, HookErrorContext — come from the root entry instead, because every service shares them:

import type { HookResult } from '@timonwa/firebase-hooks';

Hooks whose only option is formatErrorMessage take HookErrorOptions directly rather than declaring an interface of their own. See Core.

onIdToken runs after a successful sign-in

With a freshly minted token, so you can trade it for a server session. Throwing inside it aborts the flow, and the error surfaces like any other failure.

const { login } = useLogin({
  onIdToken: (idToken, user) => createSession(idToken),
});

See Server sessions.

currentPassword triggers reauthentication

Firebase rejects sensitive operations on a stale sign-in. Pass currentPassword to useUpdatePassword, useUpdateEmail, or useDeleteAccount and the hook reauthenticates first.

Omit it — OAuth-only accounts have no password — and auth/requires-recent-login reaches you through code and cause, so you can run your own policy with useReauthenticate.

Provider options are defaults, hook options win

onIdToken, onBeforeSignOut, actionCodeSettings, formatErrorMessage, and the onError observer can all be set once on AuthProvider.

A hook's own option overrides the provider, and an explicit null opts that flow out entirely.

<AuthProvider auth={auth} onIdToken={createSession}>
  {children}
</AuthProvider>;

const { login } = useLogin(); // inherits session minting
const { login } = useLogin({ onIdToken: null }); // opts out

On this page