or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-use-sync-external-store

Backwards compatible shim for React's useSyncExternalStore that works with any React that supports hooks.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/use-sync-external-store@1.5.x

To install, run

npx @tessl/cli install tessl/npm-use-sync-external-store@1.5.0

index.mddocs/

use-sync-external-store

use-sync-external-store is a backwards-compatible shim for React's useSyncExternalStore hook that enables synchronization with external data stores across React versions. It provides seamless integration with external stores while maintaining compatibility with React 16.8+ through React 19+, offering both native React 18+ performance and fallback implementations for older versions.

Package Information

  • Package Name: use-sync-external-store
  • Package Type: npm
  • Language: JavaScript/TypeScript (Flow annotated)
  • Installation: npm install use-sync-external-store

Core Imports

For React 18+ (uses native implementation):

import { useSyncExternalStore } from 'use-sync-external-store';
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector';

For React 16.8-17.x compatibility (uses shim implementation):

import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';

For React Native (uses shim implementation optimized for React Native):

// Automatically resolves to React Native version when bundled for React Native
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';

CommonJS imports:

const { useSyncExternalStore } = require('use-sync-external-store');
const { useSyncExternalStoreWithSelector } = require('use-sync-external-store/with-selector');

CommonJS with shim:

const { useSyncExternalStore } = require('use-sync-external-store/shim');
const { useSyncExternalStoreWithSelector } = require('use-sync-external-store/shim/with-selector');

Basic Usage

import { useSyncExternalStore } from 'use-sync-external-store/shim';

// Create a simple external store
const store = {
  state: { count: 0 },
  listeners: new Set(),
  
  getSnapshot() {
    return this.state;
  },
  
  subscribe(callback) {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback);
  },
  
  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.listeners.forEach(callback => callback());
  }
};

// Use in a React component
function Counter() {
  const state = useSyncExternalStore(
    store.subscribe.bind(store),
    store.getSnapshot.bind(store)
  );
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => store.setState({ count: state.count + 1 })}>
        Increment
      </button>
    </div>
  );
}

Architecture

The package provides two main hooks with multiple implementation strategies:

  • Native Implementation: Direct re-export of React 18+ built-in useSyncExternalStore
  • Shim Implementation: Custom implementation for React 16.8-17.x with identical API
  • Selector Optimization: Enhanced version with selector functions to minimize re-renders
  • Environment Detection: Automatic server/client and React Native environment handling through conditional exports and runtime detection
  • Automatic Fallback: The shim automatically detects if React's built-in useSyncExternalStore is available and uses it, otherwise falls back to custom implementation
  • Cross-version Compatibility: Single codebase supporting React 16.8 through 19+

Capabilities

Core Store Synchronization

Basic hook for synchronizing with external stores, providing automatic subscription management and React concurrent features compatibility.

/**
 * Subscribes to an external store and returns its current snapshot
 * @param subscribe - Function that registers a callback for store changes, returns unsubscribe function
 * @param getSnapshot - Function that returns the current snapshot of the store
 * @param getServerSnapshot - Optional function that returns server-side snapshot for SSR
 * @returns Current value of the external store
 */
function useSyncExternalStore<Snapshot>(
  subscribe: (onStoreChange: () => void) => () => void,
  getSnapshot: () => Snapshot,
  getServerSnapshot?: () => Snapshot
): Snapshot;

Usage Examples:

import { useSyncExternalStore } from 'use-sync-external-store/shim';

// Basic store synchronization
function useCounterStore() {
  return useSyncExternalStore(
    counterStore.subscribe,
    counterStore.getSnapshot
  );
}

// With server-side rendering support
function useAuthStore() {
  return useSyncExternalStore(
    authStore.subscribe,
    authStore.getSnapshot,
    () => ({ user: null, isAuthenticated: false }) // SSR fallback
  );
}

// Window resize example
function useWindowSize() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => ({ width: window.innerWidth, height: window.innerHeight }),
    () => ({ width: 0, height: 0 }) // SSR safe
  );
}

Optimized Store Synchronization with Selector

Enhanced version that accepts a selector function to extract specific data from the store, minimizing re-renders when only selected portions change.

/**
 * Subscribes to an external store with selector optimization
 * @param subscribe - Function that registers a callback for store changes
 * @param getSnapshot - Function that returns the current snapshot of the store  
 * @param getServerSnapshot - Server-side snapshot function or null
 * @param selector - Function to extract specific data from snapshot
 * @param isEqual - Optional equality comparison function for selections
 * @returns Selected value from the external store
 */
function useSyncExternalStoreWithSelector<Snapshot, Selection>(
  subscribe: (onStoreChange: () => void) => () => void,
  getSnapshot: () => Snapshot,
  getServerSnapshot: (() => Snapshot) | null | undefined,
  selector: (snapshot: Snapshot) => Selection,
  isEqual?: (a: Selection, b: Selection) => boolean
): Selection;

Usage Examples:

import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';

// Select specific user data to minimize re-renders
function useCurrentUserId() {
  return useSyncExternalStoreWithSelector(
    userStore.subscribe,
    userStore.getSnapshot,
    null,
    (state) => state.currentUser?.id,
    (a, b) => a === b
  );
}

// Select and transform data
function useActiveUsers() {
  return useSyncExternalStoreWithSelector(
    userStore.subscribe,
    userStore.getSnapshot,
    () => [],
    (state) => state.users.filter(user => user.isActive),
    (a, b) => a.length === b.length && a.every((user, i) => user.id === b[i].id)
  );
}

// Shopping cart total example
function useCartTotal() {
  return useSyncExternalStoreWithSelector(
    cartStore.subscribe,
    cartStore.getSnapshot,
    () => ({ items: [] }),
    (state) => state.items.reduce((total, item) => total + item.price * item.quantity, 0)
  );
}

Types

/**
 * Store subscription function type
 * Registers a callback to be called when store changes
 * Must return an unsubscribe function
 */
type Subscribe = (onStoreChange: () => void) => () => void;

/**
 * Snapshot getter function type
 * Returns the current state of the external store
 */
type GetSnapshot<T> = () => T;

/**
 * Server snapshot getter function type  
 * Returns server-safe snapshot for SSR
 */
type GetServerSnapshot<T> = () => T;

/**
 * Selector function type
 * Extracts specific data from store snapshot
 */
type Selector<Snapshot, Selection> = (snapshot: Snapshot) => Selection;

/**
 * Equality comparison function type
 * Compares two selected values for equality
 */
type IsEqual<Selection> = (a: Selection, b: Selection) => boolean;

Environment Support

React Native

The package includes React Native-specific implementations:

// Automatically resolves to React Native version on React Native
import { useSyncExternalStore } from 'use-sync-external-store/shim';

Server-Side Rendering

Both hooks support SSR through the getServerSnapshot parameter:

function useClientOnlyStore() {
  return useSyncExternalStore(
    store.subscribe,
    store.getSnapshot,
    () => null // Safe server fallback
  );
}

Development vs Production

The package provides optimized production builds with development warnings removed:

  • Development: Comprehensive error messages and warnings, including detection warnings for unsupported React versions
  • Production: Optimized bundle size with minimal runtime overhead and all debug code stripped out

Implementation Notes

The shim implementation for older React versions uses a carefully crafted approach that respects React's rules while providing the same API:

  • Uses useState and useEffect internally to manage subscriptions and state updates
  • Implements manual snapshot comparison using Object.is to detect changes
  • Handles server-side rendering by falling back to client-side implementation patterns
  • Maintains referential stability of callback functions to prevent unnecessary re-subscriptions

Error Handling

Development Mode Warnings:

  • Using main entry point (use-sync-external-store) without React 18+ shows a detailed console error explaining migration to /shim entry point
  • The warning specifically states: "The main 'use-sync-external-store' entry point is not supported; all it does is re-export useSyncExternalStore from the 'react' package, so it only works with React 18+. If you wish to support React 16 and 17, import from 'use-sync-external-store/shim' instead."
  • Inconsistent snapshots between renders trigger development-only errors in the shim implementation

Common Patterns:

// Safe error handling with fallbacks
function useSafeExternalStore(store, fallback) {
  try {
    return useSyncExternalStore(
      store.subscribe,
      store.getSnapshot,
      () => fallback
    );
  } catch (error) {
    console.warn('Store synchronization failed:', error);
    return fallback;
  }
}

Migration Guide

From React 18+ Native Hook

Replace direct React imports:

// Before
import { useSyncExternalStore } from 'react';

// After (for cross-version compatibility)
import { useSyncExternalStore } from 'use-sync-external-store/shim';

From Legacy State Management

Convert class-based or subscription patterns:

// Legacy subscription pattern
class LegacyStore {
  constructor() {
    this.state = initialState;
    this.listeners = [];
  }
  
  subscribe(callback) {
    this.listeners.push(callback);
    return () => {
      const index = this.listeners.indexOf(callback);
      if (index > -1) this.listeners.splice(index, 1);
    };
  }
  
  getSnapshot() {
    return this.state;
  }
}

// Modern usage
function useModernStore() {
  return useSyncExternalStore(
    legacyStore.subscribe.bind(legacyStore),
    legacyStore.getSnapshot.bind(legacyStore)
  );
}