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

@ekm1/react-when

v1.0.1

Published

Angular-inspired deferred loading for React with compound triggers, prefetching, and advanced performance optimizations

Readme

@ekm1/react-when

CI/CD npm version npm downloads Bundle Size TypeScript

Angular-inspired deferred loading for React - Smart, performant component loading with compound triggers, prefetching, and zero layout shift.

✨ Features

  • Multiple Triggers: viewport, interaction, timer, idle, condition, immediate
  • Compound Triggers: Combine multiple conditions (e.g., viewport + interaction)
  • Smart Prefetching: Conservative, moderate, or aggressive strategies
  • Zero Layout Shift: Consistent dimensions throughout loading states
  • Optimized Performance: Memoized hooks, efficient re-renders
  • Customizable: Bring your own styling and loading states
  • TypeScript: Full type safety included

🚀 Quick Start

npm install @ekm1/react-when
import { When } from '@ekm1/react-when';
import { lazy } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));

function App() {
  return (
    <When
      triggers="viewport"
      placeholder={() => <div className="h-64 bg-gray-200 rounded">Loading chart...</div>}
      loading={() => <div className="h-64 bg-gray-200 rounded animate-pulse">Almost ready...</div>}
    >
      <HeavyChart />
    </When>
  );
}

📚 Trigger Types

🖥️ Viewport Triggers

Load when component enters the viewport:

// Load once when entering viewport
<When triggers="viewport-once">
  <ExpensiveComponent />
</When>

// Load every time entering viewport
<When triggers="viewport">
  <RefreshableComponent />
</When>

// Custom intersection options
<When 
  triggers="viewport"
  intersectionOptions={{ threshold: 0.5, rootMargin: '100px' }}
>
  <Component />
</When>

👆 Interaction Triggers

Load on user interaction:

// Default events: click, mouseenter
<When triggers="interaction">
  <InteractiveMap />
</When>

// Custom interaction events
<When 
  triggers="interaction"
  interactionEvents={['click', 'focus', 'touchstart']}
>
  <TouchComponent />
</When>

⏱️ Timer Triggers

Load after a delay:

<When 
  triggers="timer"
  delay={3000} // 3 seconds
>
  <DelayedWidget />
</When>

🧘 Idle Triggers

Load when browser is idle:

<When 
  triggers="idle"
  idleOptions={{ timeout: 5000 }}
>
  <BackgroundTask />
</When>

🔐 Condition Triggers

Load based on state/props:

function UserContent({ isAuthenticated }) {
  return (
    <When 
      triggers="condition"
      condition={isAuthenticated}
    >
      <PrivateContent />
    </When>
  );
}

⚡ Immediate Triggers

Load immediately:

<When triggers="immediate">
  <AlwaysLoadComponent />
</When>

🎯 Compound Triggers

Combine multiple triggers for advanced loading strategies:

// Load when BOTH in viewport AND user interacts
<When triggers={['viewport', 'interaction']}>
  <ExpensiveInteractiveMap />
</When>

// Load when BOTH in viewport AND after 5 seconds
<When 
  triggers={['viewport', 'timer']}
  delay={5000}
>
  <ComponentThatNeedsBoth />
</When>

// Load when idle AND authenticated
<When 
  triggers={['idle', 'condition']}
  condition={user.isLoggedIn}
>
  <UserAnalytics />
</When>

🚀 Prefetching Strategies

Optimize loading with smart prefetching:

// Conservative: prefetch when ALL triggers are nearly satisfied
<When 
  triggers={['viewport', 'interaction']}
  prefetchStrategy="conservative"
>
  <Component />
</When>

// Moderate: prefetch when ANY trigger is satisfied  
<When 
  triggers={['viewport', 'interaction']}
  prefetchStrategy="moderate"
>
  <Component />
</When>

// Aggressive: prefetch immediately
<When 
  triggers="viewport"
  prefetchStrategy="aggressive"
>
  <Component />
</When>

// None: disable prefetching
<When 
  triggers="interaction"
  prefetchStrategy="none"
>
  <Component />
</When>

🎨 Custom Loading States

Prevent layout shift with properly sized loading states:

<When
  triggers="viewport"
  
  // Before triggers are met
  placeholder={({ timerState, interactions }) => (
    <div className="h-64 bg-gray-100 rounded flex items-center justify-center">
      <div className="text-center">
        <div className="animate-spin w-6 h-6 border-2 border-blue-500 rounded-full mb-2" />
        <p>Waiting for viewport...</p>
        {timerState && <p>Timer: {timerState.remaining}ms</p>}
      </div>
    </div>
  )}
  
  // While loading
  loading={({ progress, prefetched }) => (
    <div className="h-64 bg-gray-100 rounded flex items-center justify-center">
      <div className="text-center">
        <div className="animate-spin w-6 h-6 border-2 border-green-500 rounded-full mb-2" />
        <p>{prefetched ? 'Using prefetched data...' : 'Loading component...'}</p>
        {progress && <div className="w-32 bg-gray-300 rounded-full h-2">
          <div className="bg-green-500 h-2 rounded-full" style={{width: `${progress}%`}} />
        </div>}
      </div>
    </div>
  )}
  
  // Error handling
  error={({ errorCount, retry }) => (
    <div className="h-64 bg-red-100 border border-red-300 rounded flex items-center justify-center">
      <div className="text-center">
        <p className="text-red-800 mb-2">Failed to load ({errorCount} attempts)</p>
        <button onClick={retry} className="px-4 py-2 bg-red-500 text-white rounded">
          Retry
        </button>
      </div>
    </div>
  )}
>
  <YourComponent />
</When>

⚙️ Advanced Configuration

Minimum Loading Time

Prevent flickering with minimum loading duration:

<When 
  triggers="viewport"
  minimumLoading={500} // Always show loading for at least 500ms
>
  <FastLoadingComponent />
</When>

Retry Logic

Automatic retries on component load failures:

<When 
  triggers="interaction"
  retryCount={3} // Retry up to 3 times
  onLoadError={(error, errorInfo) => {
    console.error('Component failed to load:', error);
    analytics.track('component_load_error', errorInfo);
  }}
>
  <Component />
</When>

Accessibility

Built-in ARIA support:

<When 
  triggers="viewport"
  aria-label="Loading analytics dashboard"
  className="focus:ring-2 focus:ring-blue-500"
>
  <Dashboard />
</When>

📊 Real-World Examples

Interactive Analytics Dashboard

function AnalyticsDashboard() {
  const [selectedTimeRange, setSelectedTimeRange] = useState('7d');
  
  return (
    <div className="space-y-6">
      {/* Load immediately - critical data */}
      <When triggers="immediate">
        <KeyMetrics />
      </When>
      
      {/* Load when scrolled into view */}
      <When triggers="viewport" prefetchStrategy="conservative">
        <TrafficChart timeRange={selectedTimeRange} />
      </When>
      
      {/* Load only when user shows interest */}
      <When triggers="interaction" interactionEvents={['click', 'mouseenter']}>
        <DetailedAnalytics />
      </When>
      
      {/* Load in background when idle */}
      <When triggers="idle">
        <ReportGenerator />
      </When>
    </div>
  );
}

Progressive Content Loading

function ArticleReader({ article }) {
  const [showComments, setShowComments] = useState(false);
  
  return (
    <article>
      {/* Critical content loads immediately */}
      <When triggers="immediate">
        <ArticleHeader article={article} />
      </When>
      
      <When triggers="immediate">
        <ArticleContent content={article.content} />
      </When>
      
      {/* Related articles load when scrolled */}
      <When triggers="viewport" prefetchStrategy="moderate">
        <RelatedArticles articleId={article.id} />
      </When>
      
      {/* Comments load conditionally */}
      <When 
        triggers="condition"
        condition={showComments}
        minimumLoading={300}
      >
        <CommentSection articleId={article.id} />
      </When>
      
      <button onClick={() => setShowComments(true)}>
        Load Comments
      </button>
    </article>
  );
}

🔧 API Reference

When Component Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | triggers | TriggerType \| TriggerType[] | ['viewport'] | When to load the component | | delay | number | 0 | Delay in ms for timer trigger | | condition | boolean | true | Boolean condition for condition trigger | | prefetchStrategy | 'none' \| 'conservative' \| 'moderate' \| 'aggressive' | 'conservative' | Prefetching strategy | | minimumLoading | number | 0 | Minimum loading duration in ms | | placeholder | ReactNode \| Function | null | Content shown before triggers are met | | loading | ReactNode \| Function | null | Content shown while loading | | error | ReactNode \| Function | null | Content shown on error | | onLoad | () => void | () => {} | Called when loading starts | | onLoadError | (error: Error) => void | () => {} | Called when loading fails | | intersectionOptions | IntersectionObserverInit | {} | Options for viewport triggers | | idleOptions | IdleRequestOptions | {} | Options for idle trigger | | interactionEvents | string[] | ['click', 'mouseenter'] | Events for interaction trigger | | retryCount | number | 3 | Number of retry attempts on error |

Trigger Types

  • 'viewport' - Load when entering viewport (every time)
  • 'viewport-once' - Load when entering viewport (once only)
  • 'interaction' - Load on user interaction
  • 'timer' - Load after delay
  • 'idle' - Load when browser is idle
  • 'condition' - Load when condition is true
  • 'immediate' - Load immediately

Function Props

Placeholder Function Props

interface PlaceholderProps {
  timerState?: {
    expired: boolean;
    progress: number;
    remaining: number;
  };
  interactions?: {
    [eventType: string]: number;
  };
  idleTime?: number;
}

Loading Function Props

interface LoadingProps {
  progress?: number;
  prefetched?: boolean;
}

Error Function Props

interface ErrorProps {
  errorCount: number;
  retry: () => void;
}

🎯 Best Practices

1. Prevent Layout Shift

Always match dimensions between placeholder, loading, and final component:

// ❌ Bad - causes layout shift
<When 
  triggers="viewport"
  placeholder={() => <div>Loading...</div>}
>
  <div className="h-64 w-full bg-blue-500">Large Component</div>
</When>

// ✅ Good - consistent dimensions
<When 
  triggers="viewport"
  placeholder={() => <div className="h-64 w-full bg-gray-200 animate-pulse" />}
  loading={() => <div className="h-64 w-full bg-gray-300 animate-pulse" />}
>
  <div className="h-64 w-full bg-blue-500">Large Component</div>
</When>

2. Choose the Right Triggers

  • Critical content: immediate
  • Above-the-fold: immediate or viewport
  • Below-the-fold: viewport-once
  • Interactive features: interaction
  • Background tasks: idle
  • Conditional content: condition

3. Optimize Bundle Size

Use code splitting with React.lazy:

const HeavyComponent = lazy(() => import('./HeavyComponent'));

<When triggers="interaction">
  <HeavyComponent />
</When>

4. Handle Errors Gracefully

Always provide error states:

<When 
  triggers="viewport"
  error={({ retry, errorCount }) => (
    <div className="p-4 border border-red-300 rounded">
      <p>Failed to load component (attempt {errorCount})</p>
      <button onClick={retry}>Try Again</button>
    </div>
  )}
>
  <Component />
</When>

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

git clone https://github.com/ekm1/react-when.git
cd react-when
npm install

# Run tests
npm test

# Build library
npm run build

# Run example
npm run example

📄 License

MIT © Migel Hoxha


📖 More Examples

Check out our interactive examples to see all features in action!

Happy deferred loading! 🚀