npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@periodic/tungsten-react

v1.0.0

Published

Official React integration for @periodic/tungsten-client — provider, hooks, and guards with minimal re-renders

Downloads

12

Readme

⚛️ Periodic Tungsten React

npm version License: MIT TypeScript

Official React integration for @periodic/tungsten-client — provider, hooks, and guards with minimal re-renders

Part of the Periodic series of Node.js packages by Uday Thakur.


💡 Why Tungsten React?

@periodic/tungsten-react is the React integration layer for @periodic/tungsten-client. While @periodic/tungsten-client handles the browser authentication lifecycle — token storage, proactive refresh, multi-tab sync, and race protection — this library makes that lifecycle reactive: an AuthProvider that bridges the client's event system into React state, hooks that expose auth state and actions to any component in the tree, and a RequireAuth guard that handles redirects declaratively.

Most React auth integrations are hand-rolled: a useEffect that subscribes to some auth object, a context that re-renders the entire tree on every token refresh, and a protected route component that each developer reimplements slightly differently. Tungsten React gives you one canonical pattern, subscribed at the right granularity, with no unnecessary re-renders.

The name represents:

  • Reactivity: Auth state changes propagate to components without polling
  • Composability: AuthProvider, useAuth, useAccessToken, and RequireAuth compose cleanly
  • Minimalism: Thin integration layer — all auth logic stays in @periodic/tungsten-client
  • Correctness: SSR-safe, StrictMode-safe, and subscription-cleanup handled correctly

Just as @periodic/tungsten-client handles browser auth without magic, @periodic/tungsten-react handles React integration without reinventing the wheel.


🎯 Why Choose Tungsten React?

React authentication integrations get the subtle parts wrong more often than not:

  • Context re-renders the entire tree on token refresh — every component that consumes auth context re-renders, even when only the token changed
  • No cleanup on unmountuseEffect subscriptions leak, causing state updates on unmounted components
  • SSR errors — accessing localStorage or window at render time crashes in SSR environments
  • StrictMode double-invocation — effects run twice in development, causing double subscriptions or double logouts
  • No access token hook — components that only need a token for API calls are forced to depend on the full auth context
  • Bespoke protected route implementations — every team writes a slightly different PrivateRoute component

Periodic Tungsten React provides the perfect solution:

AuthProvider — bridges TungstenClient events into React context with a single subscription
useAuth() — access full auth state, login, logout, and client from any component
useAccessToken() — access only the current token — no re-renders on unrelated state changes
useRequireAuth() — redirect unauthenticated users programmatically
RequireAuth — declarative guard component for protected routes
Minimal re-renders — components subscribe at the right granularity
SSR-safe — no window or localStorage access at render time
StrictMode-safe — subscription and cleanup handled correctly
Typed — all hooks and components are fully TypeScript-typed
Zero opinions on routing — works with React Router, TanStack Router, Next.js, or none
No global state — multiple AuthProvider instances are fully isolated
Production-ready — non-blocking, never crashes your app


📦 Installation

npm install @periodic/tungsten-react @periodic/tungsten-client react

Or with yarn:

yarn add @periodic/tungsten-react @periodic/tungsten-client react

🚀 Quick Start

import { TungstenClient } from '@periodic/tungsten-client';
import { AuthProvider, useAuth, RequireAuth } from '@periodic/tungsten-react';

// 1. Create the client
const client = new TungstenClient({
  refreshEndpoint: '/api/auth/refresh',
});

// 2. Wrap your app
function App() {
  return (
    <AuthProvider client={client}>
      <Router />
    </AuthProvider>
  );
}

// 3. Use auth state in any component
function Dashboard() {
  const { state, logout } = useAuth();

  if (state.status !== 'authenticated') {
    return <p>Please log in</p>;
  }

  return (
    <div>
      <h1>Welcome back</h1>
      <button onClick={logout}>Sign out</button>
    </div>
  );
}

// 4. Protect routes declaratively
function ProfilePage() {
  return (
    <RequireAuth redirectTo="/login">
      <Profile />
    </RequireAuth>
  );
}

Auth state shape after login:

{
  "status": "authenticated",
  "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
  "expiresAt": 1708000900000,
  "authenticatedAt": 1708000000000
}

🧠 Core Concepts

AuthProvider

  • The single integration point between TungstenClient and React
  • Subscribes to client.on('stateChange') once, on mount — one subscription for the entire tree
  • Stores auth state in React context — components re-render only when state actually changes
  • Cleans up the subscription on unmount — no leaks, safe in StrictMode
  • Accepts the client instance as a prop — no global singleton, safe for SSR and testing
const client = new TungstenClient({ refreshEndpoint: '/api/auth/refresh' });

<AuthProvider client={client}>
  <App />
</AuthProvider>

useAuth()

  • Returns the current AuthStateData, login, logout, and the client instance
  • Re-renders the component whenever auth state transitions (login, logout, refresh, expiry)
  • Use this when your component cares about the full auth state — login pages, nav bars, user menus
const { state, login, logout, client } = useAuth();

useAccessToken()

  • Returns only the current access token string
  • Re-renders only when the token itself changes (not on unrelated state transitions)
  • Use this in components that only need the token for API calls — avoids re-renders on refreshing transitions
const accessToken = useAccessToken();

RequireAuth

  • Renders children when authenticated, redirects when not
  • Design principle: declarative guards belong in JSX, not in route configuration scattered across the app
<RequireAuth redirectTo="/login">
  <ProtectedPage />
</RequireAuth>

✨ Features

🏠 AuthProvider

Bridge TungstenClient into React with a single subscription:

import { TungstenClient } from '@periodic/tungsten-client';
import { AuthProvider } from '@periodic/tungsten-react';

const client = new TungstenClient({
  refreshEndpoint: '/api/auth/refresh',
  refreshThresholdMs: 60_000,
});

function Root() {
  return (
    <AuthProvider client={client}>
      <App />
    </AuthProvider>
  );
}

🪝 useAuth()

Full auth state and actions from any component:

import { useAuth } from '@periodic/tungsten-react';

function NavBar() {
  const { state, logout } = useAuth();

  return (
    <nav>
      {state.status === 'authenticated' ? (
        <button onClick={logout}>Sign out</button>
      ) : (
        <a href="/login">Sign in</a>
      )}
    </nav>
  );
}

🔑 useAccessToken()

Access the current token without subscribing to the full state:

import { useAccessToken } from '@periodic/tungsten-react';

function ApiComponent() {
  const accessToken = useAccessToken();

  async function fetchData() {
    const res = await fetch('/api/data', {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    return res.json();
  }
}

🛡️ RequireAuth

Declarative route protection:

import { RequireAuth } from '@periodic/tungsten-react';

function App() {
  return (
    <Routes>
      <Route path="/login" element={<LoginPage />} />
      <Route
        path="/dashboard"
        element={
          <RequireAuth redirectTo="/login">
            <Dashboard />
          </RequireAuth>
        }
      />
    </Routes>
  );
}

🔄 useRequireAuth()

Programmatic redirect for components that need imperative auth checks:

import { useRequireAuth } from '@periodic/tungsten-react';

function CheckoutPage() {
  useRequireAuth({ redirectTo: '/login', returnTo: '/checkout' });
  return <Checkout />;
}

📚 Common Patterns

1. Login Form

import { useAuth } from '@periodic/tungsten-react';

function LoginForm() {
  const { login } = useAuth();

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const email = (form.elements.namedItem('email') as HTMLInputElement).value;
    const password = (form.elements.namedItem('password') as HTMLInputElement).value;

    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });

    if (res.ok) {
      const { accessToken, refreshToken } = await res.json();
      login({ accessToken, refreshToken }); // → state: authenticated
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      <button type="submit">Sign in</button>
    </form>
  );
}

2. Protected Route with React Router

import { Routes, Route, Navigate } from 'react-router-dom';
import { RequireAuth } from '@periodic/tungsten-react';

function AppRoutes() {
  return (
    <Routes>
      <Route path="/login" element={<LoginPage />} />
      <Route
        path="/dashboard"
        element={<RequireAuth redirectTo="/login"><Dashboard /></RequireAuth>}
      />
      <Route
        path="/settings"
        element={<RequireAuth redirectTo="/login"><Settings /></RequireAuth>}
      />
    </Routes>
  );
}

3. Conditional UI Based on Auth State

import { useAuth } from '@periodic/tungsten-react';
import type { AuthStateData } from '@periodic/tungsten-client';

function AuthStatus() {
  const { state } = useAuth();

  const statusLabel: Record<AuthStateData['status'], string> = {
    unauthenticated: 'Signed out',
    authenticating: 'Signing in...',
    authenticated: 'Signed in',
    refreshing: 'Refreshing session...',
    expired: 'Session expired',
    error: 'Auth error',
  };

  return <span>{statusLabel[state.status]}</span>;
}

4. Authenticated Data Fetching

import { useAuth } from '@periodic/tungsten-react';
import { useEffect, useState } from 'react';

function UserProfile() {
  const { client, state } = useAuth();
  const [profile, setProfile] = useState(null);

  useEffect(() => {
    if (state.status !== 'authenticated') return;
    // client.fetch() handles token injection and refresh automatically
    client.fetch('/api/me').then(r => r.json()).then(setProfile);
  }, [client, state.status]);

  if (!profile) return <p>Loading...</p>;
  return <div>{profile.name}</div>;
}

5. Session Expiry Handling

import { useAuth } from '@periodic/tungsten-react';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

function SessionWatcher() {
  const { state } = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    if (state.status === 'expired') {
      navigate('/login?reason=expired');
    }
  }, [state.status, navigate]);

  return null; // render anywhere in the tree
}

6. Loading State During Initial Hydration

import { useAuth } from '@periodic/tungsten-react';

function AppShell({ children }: { children: React.ReactNode }) {
  const { state } = useAuth();

  // Show a loading screen while the client checks for an existing session
  if (state.status === 'authenticating') {
    return <LoadingScreen />;
  }

  return <>{children}</>;
}

7. Structured Logging Integration

import { useEffect } from 'react';
import { useAuth } from '@periodic/tungsten-react';
import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

function AuthLogger() {
  const { state } = useAuth();

  useEffect(() => {
    logger.info('tungsten.react.state_change', { status: state.status });
  }, [state.status]);

  return null;
}

8. Production Setup

import { TungstenClient } from '@periodic/tungsten-client';
import { AuthProvider } from '@periodic/tungsten-react';

const isDevelopment = process.env.NODE_ENV === 'development';

const client = new TungstenClient({
  refreshEndpoint: '/api/auth/refresh',
  storage: isDevelopment ? sessionStorage : localStorage,
  refreshThresholdMs: isDevelopment ? 5_000 : 60_000,
  onStateChange: (state) => {
    if (state.status === 'error') {
      Sentry.captureException(state.error);
    }
  },
});

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider client={client}>
      {children}
    </AuthProvider>
  );
}

🎛️ Configuration Options

AuthProvider Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | client | TungstenClient | required | The TungstenClient instance to bind | | children | React.ReactNode | required | Child components |

RequireAuth Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | React.ReactNode | required | Content to render when authenticated | | redirectTo | string | required | Path to redirect to when not authenticated | | returnTo | string | — | Appended as ?returnTo= query param on redirect |

useRequireAuth Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | redirectTo | string | required | Path to redirect to when not authenticated | | returnTo | string | — | Appended as ?returnTo= query param on redirect |

useAuth Return Value

| Field | Type | Description | |-------|------|-------------| | state | AuthStateData | Current auth state (discriminated union) | | login | (tokens: AuthTokens) => void | Login and store tokens | | logout | () => void | Logout and clear tokens | | client | TungstenClient | The underlying client instance |


📋 API Reference

Components

<AuthProvider client={TungstenClient} />
<RequireAuth redirectTo="/login" returnTo="/dashboard" />

Hooks

useAuth(): { state: AuthStateData; login: (tokens: AuthTokens) => void; logout: () => void; client: TungstenClient }
useAccessToken(): string | null
useRequireAuth(options: { redirectTo: string; returnTo?: string }): void

Types

import type { AuthProviderProps, RequireAuthProps } from '@periodic/tungsten-react';
import type { AuthStateData, AuthTokens } from '@periodic/tungsten-client';

🧩 Architecture

@periodic/tungsten-react/
├── src/
│   ├── AuthProvider.tsx       # AuthProvider component + useAuth, useAccessToken, useRequireAuth hooks
│   ├── RequireAuth.tsx        # RequireAuth guard component
│   ├── context.ts             # React context definition
│   ├── types.ts               # AuthProviderProps, RequireAuthProps
│   └── index.ts               # Public API

Design Philosophy:

  • One subscriptionAuthProvider subscribes once and owns the React state; hooks read from context
  • Thin layer — all auth logic stays in @periodic/tungsten-client; this library only bridges events to React
  • No routing opinionsRequireAuth accepts a redirectTo string and uses window.location by default; override for your router
  • No global state — the client is passed as a prop; multiple providers in the same tree are fully isolated
  • StrictMode-safe — the subscription in useEffect is idempotent and the cleanup is always correct

📈 Performance

  • One context re-render per state transition — the entire tree only re-renders when auth state actually changes
  • useAccessToken re-renders less — only updates when the token string changes, not on refreshing transitions
  • No polling — state changes are event-driven via TungstenClient.on('stateChange')
  • No extra subscriptions — all hooks read from a single context value, no additional listeners

🚫 Explicit Non-Goals

This package intentionally does not include:

❌ Browser token lifecycle management (use @periodic/tungsten-client)
❌ Server-side token signing or verification (use @periodic/tungsten)
❌ A built-in router integration — pass redirectTo and handle navigation yourself
❌ A login form or any UI components
❌ OAuth / OpenID Connect flows
❌ Magic or implicit behavior on import
❌ Configuration files (configure in code)

Focus on doing one thing well: idiomatic React bindings for @periodic/tungsten-client.


🎨 TypeScript Support

Full TypeScript support with complete type safety:

import type { AuthProviderProps, RequireAuthProps } from '@periodic/tungsten-react';
import type { AuthStateData } from '@periodic/tungsten-client';

// useAuth returns a fully typed object
const { state } = useAuth();
if (state.status === 'authenticated') {
  state.accessToken; // string — only present in this branch
}

// RequireAuth props are typed
<RequireAuth redirectTo="/login" returnTo={location.pathname}>
  <ProtectedPage />
</RequireAuth>

🧪 Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

Tests use @testing-library/react and a mock TungstenClient.

Note: All tests achieve >80% code coverage.


🤝 Related Packages

Part of the Periodic series by Uday Thakur:

Build complete, production-ready APIs with the Periodic series!


📖 Documentation


🛠️ Production Recommendations

Environment Variables

NODE_ENV=production
NEXT_PUBLIC_API_URL=https://api.example.com

Log Aggregation

Pair with @periodic/iridium for structured JSON output:

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

// Pass onStateChange to the TungstenClient, not the AuthProvider
const client = new TungstenClient({
  refreshEndpoint: '/api/auth/refresh',
  onStateChange: (state) => {
    logger.info('tungsten.react.state_change', { status: state.status });
    if (state.status === 'error') {
      logger.error('tungsten.react.error', { error: state.error?.message });
    }
  },
});

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.

Error Monitoring

const client = new TungstenClient({
  refreshEndpoint: '/api/auth/refresh',
  onStateChange: (state) => {
    if (state.status === 'error') {
      Sentry.captureException(state.error, { extra: { authStatus: state.status } });
    }
  },
});

📝 License

MIT © Uday Thakur


🙏 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details on:

  • Code of conduct
  • Development setup
  • Pull request process
  • Coding standards
  • Architecture principles

📞 Support


🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!


Built with ❤️ by Uday Thakur for production-grade Node.js applications