CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-use-sync-external-store

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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)
  );
}
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/use-sync-external-store@1.5.x
Publish Source
CLI
Badge
tessl/npm-use-sync-external-store badge