React Quick Start

Get running with encrypted state in 5 minutes.

Install

npm install @p47h/vault-react

1. Add the Provider

The provider initializes and owns the Vault lifecycle. It loads the WASM core, manages authentication state, and exposes a consistent Vault instance to the component tree.

// main.tsx
import { P47hProvider } from "@p47h/vault-react";
import App from "./App";

createRoot(document.getElementById("root")!).render(
  <P47hProvider>
    <App />
  </P47hProvider>
);

The provider is a singleton. Mount it once at the root.

2. Handle Identity

Use useIdentity to manage registration, login, and logout without leaking keys or managing crypto state manually.

import { useIdentity } from "@p47h/vault-react";

function AuthForm() {
  const { register, login, logout, isAuthenticated, isLoading } = useIdentity();

  if (isLoading) return <p>Loading...</p>;

  if (isAuthenticated) {
    return <button onClick={logout}>Lock Vault</button>;
  }

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const password = e.target.password.value;
      login(password);
    }}>
      <input name="password" type="password" />
      <button type="submit">Unlock</button>
    </form>
  );
}

3. Use Encrypted State

Secrets behave like React state. They load automatically when the vault unlocks and become unavailable when it locks.

import { useSecret } from "@p47h/vault-react";

function ApiKeyManager() {
  const [apiKey, setApiKey, { isLoading, error }] = useSecret("stripe_key");

  if (isLoading) return <p>Decrypting...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <input 
        value={apiKey ?? ""} 
        onChange={(e) => setApiKey(e.target.value)}
        placeholder="sk_live_..."
      />
    </div>
  );
}

When setApiKey is called, the value is encrypted and persisted to IndexedDB. The plaintext never touches localStorage or sessionStorage.

Complete Example

import { P47hProvider, useIdentity, useSecret } from "@p47h/vault-react";

function App() {
  const { isAuthenticated } = useIdentity();

  return (
    <div>
      <AuthForm />
      {isAuthenticated && <SecretEditor />}
    </div>
  );
}

function SecretEditor() {
  const [token, setToken] = useSecret("github_token");

  return (
    <input 
      value={token ?? ""} 
      onChange={(e) => setToken(e.target.value)}
    />
  );
}

// Mount
createRoot(document.getElementById("root")!).render(
  <P47hProvider>
    <App />
  </P47hProvider>
);

Error Handling

Error handling is explicit. Hooks surface loading and error states so the UI can react predictably.

const [secret, setSecret, { isLoading, error }] = useSecret("key");

// isLoading: true while decrypting or during WASM init
// error: contains Error object if operation failed

No silent failures. No magic retries.

When you probably don’t need this

Your app has no client-side secrets
All sensitive operations already happen on the backend
You require cross-device sync

Next: Hooks Reference — full API documentation for all hooks.