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

@vynelix/vynemit-react

v1.0.0

Published

ReactJs/NextJs adapter/wrapper for Vynemit library. It makes getting real-time notification from server reflect on frontend/client in real-time'

Downloads

113

Readme

vynemit-react - Provider-less Usage Guide

Complete guide for using Synq Notifications in React with react-synq-store (NO PROVIDER NEEDED!)

🚀 The Magic: No Provider Required!

Unlike traditional React state management, react-synq-store doesn't require wrapping your app in a provider. Just initialize once and use anywhere! Perfect for:

  • Global Toast Notifications
  • Web (System Tray) Notifications
  • Notification Badges (in headers, sidebars, anywhere)
  • Notification Centers (dropdowns, panels)
  • Cross-App State (shared across different parts of your app)

Installation

npm install vynemit-react react-synq-store @vynelix/vynemit-core

Quick Start (3 Steps)

Step 1: Initialize Once

Call initializeNotifications() once in your app entry point:

// app/layout.tsx (Next.js App Router)
// or _app.tsx (Next.js Pages Router)
// or main.tsx (Vite/CRA)

import { initializeNotifications } from 'vynemit-react';

// Initialize on client side
if (typeof window !== 'undefined') {
  initializeNotifications({
    config: {
      apiUrl: 'http://localhost:3000',
      userId: 'user:123',
      realtimeTransport: 'sse', // Default
      ssePath: '/notifications/:userId/stream', // Optional
      wsUrl: 'http://localhost:3000/notifications', // Optional fallback/override
      pollInterval: 5000, // Final fallback
      getAuthToken: async () => localStorage.getItem('token')
    },
    onInitialized: () => console.log('Initialized'),
    onConnected: () => console.log('Connected to realtime'),
    onNotification: (notification) => {
      // Optional: trigger global toast
      console.log('Received notification:', notification);
    }
  });
}

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        {/* No provider needed! */}
      </body>
    </html>
  );
}

Step 2: Use Anywhere

In your header:

import { useUnreadCount } from 'vynemit-react';

function Header() {
  const unreadCount = useUnreadCount(); // ✨ Works without provider!
  
  return (
    <header>
      <nav>
        <button>
          🔔
          {unreadCount > 0 && <Badge>{unreadCount}</Badge>}
        </button>
      </nav>
    </header>
  );
}

In your sidebar:

import { useNotifications } from 'vynemit-react';

function Sidebar() {
  const { notifications } = useNotifications({ status: 'unread' });
  
  return (
    <aside>
      <h3>Recent ({notifications.length})</h3>
      {notifications.map(n => <NotificationCard key={n.id} {...n} />)}
    </aside>
  );
}

Anywhere else:

import { markAsRead } from 'vynemit-react';

function RandomComponent() {
  return (
    <button onClick={() => markAsRead('notif_123')}>
      Mark as Read
    </button>
  );
}

Step 3: Build Components

All components share the same state automatically!

function NotificationCenter() {
  const {
    notifications,
    unreadCount,
    loading,
    markAsRead,
    deleteNotification
  } = useNotifications();

  return (
    <div>
      <h2>Notifications ({unreadCount} unread)</h2>
      {loading && <Spinner />}
      {notifications.map(notif => (
        <div key={notif.id}>
          <h3>{notif.title}</h3>
          <p>{notif.body}</p>
          <button onClick={() => markAsRead(notif.id)}>✓</button>
          <button onClick={() => deleteNotification(notif.id)}>×</button>
        </div>
      ))}
    </div>
  );
}

📚 API Reference

Initialization

initializeNotifications(options: { 
  config: NotificationConfig,
  onInitialized?: () => void,
  onConnected?: () => void,
  onNotification?: (notification: Notification) => void 
})

interface NotificationConfig {
  // Required
  apiUrl: string;              // Backend API URL
  userId: string;              // Current user ID
  
  // Optional - Real-time
  realtimeTransport?: 'sse' | 'websocket' | 'polling' | 'none'; // Default: 'sse'
  sseUrl?: string;             // Optional SSE base URL (defaults to apiUrl)
  ssePath?: string;            // SSE path template, default '/notifications/:userId/stream'
  sseAuthQueryParam?: string;  // If getAuthToken exists, token query key (default: 'token')
  sseConnectTimeoutMs?: number;// Time to wait before SSE fallback (default: 5000)
  wsUrl?: string;              // WebSocket URL (used when transport is websocket/fallback)
  pollInterval?: number;       // Polling interval (ms) fallback
  debug?: boolean;             // Log structured realtime diagnostics to console
  onDebugEvent?: (event) => void; // Callback for realtime lifecycle events
  
  // Optional - Auth
  getAuthToken?: () => Promise<string | null>;
}

Hooks

useNotifications()

Main hook for accessing notifications:

const {
  notifications: Notification[];
  unreadCount: number;
  loading: boolean;
  error: string | null;
  isConnected: boolean;
  
  // Actions
  markAsRead: (id: string) => Promise<void>;
  markAllAsRead: () => Promise<void>;
  markAsUnread: (id: string) => Promise<void>;
  markAllAsUnread: () => Promise<void>;
  deleteNotification: (id: string) => Promise<void>;
  deleteAll: () => Promise<void>;
  refresh: () => Promise<void>;
} = useNotifications(filters?: NotificationFilters);

With filters:

const { notifications } = useNotifications({
  status: 'unread',
  type: 'comment',
  limit: 10
});

useUnreadCount()

Optimized for badge displays (only re-renders when count changes):

const unreadCount: number = useUnreadCount();

Perfect for:

function NotificationBadge() {
  const count = useUnreadCount(); // Only re-renders on count change!
  return count > 0 ? <Badge>{count}</Badge> : null;
}

useNotificationStats()

Get statistics:

const stats: NotificationStats | null = useNotificationStats();

// stats = {
//   total: 50,
//   unread: 5,
//   byStatus: { read: 45, unread: 5, ... },
//   byChannel: { inapp: 30, email: 20 },
//   byPriority: { urgent: 2, high: 10, normal: 35, low: 3 }
// }

useNotificationPreferences()

Manage user preferences:

const {
  preferences: NotificationPreferences | null;
  updatePreferences: (prefs: Partial<NotificationPreferences>) => Promise<void>;
} = useNotificationPreferences();

useNotificationRealtime()

Inspect transport status and fallback behavior in UI:

const realtime = useNotificationRealtime();
// realtime = {
//   transport: 'sse' | 'websocket' | 'polling' | 'none' | null,
//   status: 'idle' | 'connecting' | 'connected' | 'fallback' | 'error',
//   lastEvent: string | null,
//   lastError: string | null,
//   updatedAt: Date | null
// }

useNotification()

Get single notification by ID:

const {
  notification: Notification | undefined;
  markAsRead: () => Promise<void>;
  delete: () => Promise<void>;
} = useNotification(notificationId);

Actions (Can be called anywhere!)

import {
  fetchNotifications,
  markAsRead,
  markAllAsRead,
  markAsUnread,
  markAllAsUnread,
  deleteNotification,
  deleteAll,
  updatePreferences
} from 'vynemit-react';

// Call from anywhere - no hooks needed!
await markAsRead('notif_123');
await markAllAsRead();
await markAsUnread('notif_123');
await deleteNotification('notif_456');

🎨 Real-World Examples

1. Global Toast-like Notifications

Following your toast pattern:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <NotificationToastContainer /> {/* No provider! */}
      </body>
    </html>
  );
}

// components/NotificationToastContainer.tsx
"use client";

import { AnimatePresence, motion } from "framer-motion";
import { useNotifications } from "vynemit-react";

export default function NotificationToastContainer() {
  const { notifications, markAsRead } = useNotifications({
    status: 'unread',
    priority: 'urgent'
  });

  // Auto-dismiss after 5s
  React.useEffect(() => {
    notifications.forEach(notif => {
      setTimeout(() => markAsRead(notif.id), 5000);
    });
  }, [notifications]);

  return (
    <div className="fixed bottom-4 right-4 space-y-2 z-50">
      <AnimatePresence>
        {notifications.map(notif => (
          <motion.div
            key={notif.id}
            initial={{ opacity: 0, x: 50 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: 50 }}
            className={`rounded-lg shadow-lg p-4 ${
              notif.priority === 'urgent' ? 'bg-red-600' : 'bg-blue-600'
            } text-white`}
          >
            <h3 className="font-bold">{notif.title}</h3>
            <p className="text-sm">{notif.body}</p>
          </motion.div>
        ))}
      </AnimatePresence>
    </div>
  );
}

2. Notification Dropdown

function NotificationDropdown() {
  const [open, setOpen] = useState(false);
  const { notifications, markAsRead, markAllAsRead } = useNotifications({
    limit: 10,
    sortBy: 'createdAt'
  });
  const unreadCount = useUnreadCount();

  return (
    <div>
      <button onClick={() => setOpen(!open)}>
        🔔
        {unreadCount > 0 && <Badge>{unreadCount}</Badge>}
      </button>

      {open && (
        <div className="dropdown">
          <div className="header">
            <h3>Notifications</h3>
            <button onClick={markAllAsRead}>Mark all read</button>
          </div>
          
          {notifications.map(notif => (
            <div key={notif.id} onClick={() => markAsRead(notif.id)}>
              <h4>{notif.title}</h4>
              <p>{notif.body}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

3. Multi-Location State Sync

The same state works everywhere automatically:

// Header.tsx
function Header() {
  const count = useUnreadCount();
  return <Badge>{count}</Badge>; // Shows "5"
}

// Sidebar.tsx
function Sidebar() {
  const { markAsRead } = useNotifications();
  
  const handleClick = () => {
    markAsRead('notif_123');
    // Header badge automatically updates to "4"! ✨
  };
  
  return <button onClick={handleClick}>Mark Read</button>;
}

// NotificationCenter.tsx
function NotificationCenter() {
  const { notifications } = useNotifications();
  // Automatically stays in sync with Header and Sidebar!
  return <List items={notifications} />;
}

4. Filtered Views

function NotificationTabs() {
  const [tab, setTab] = useState('all');
  
  const filters = {
    all: {},
    unread: { status: 'unread' },
    urgent: { priority: 'urgent' },
    social: { category: 'social' }
  };

  const { notifications } = useNotifications(filters[tab]);

  return (
    <div>
      <Tabs value={tab} onChange={setTab} />
      <NotificationList notifications={notifications} />
    </div>
  );
}

5. Browser Notifications

function BrowserNotificationListener() {
  const { notifications } = useNotifications({ status: 'unread' });
  const [lastCount, setLastCount] = useState(0);

  useEffect(() => {
    // Show browser notification for new items
    if (notifications.length > lastCount && 'Notification' in window) {
      const newNotif = notifications[0];
      if (Notification.permission === 'granted') {
        new Notification(newNotif.title, {
          body: newNotif.body,
          icon: '/icon.png'
        });
      }
    }
    setLastCount(notifications.length);
  }, [notifications.length]);

  return null; // Invisible component
}

6. Custom Notification Sound

function NotificationSoundPlayer() {
  const { notifications } = useNotifications();
  const prevCountRef = useRef(0);

  useEffect(() => {
    if (notifications.length > prevCountRef.current) {
      const audio = new Audio('/notification.mp3');
      audio.play();
    }
    prevCountRef.current = notifications.length;
  }, [notifications.length]);

  return null;
}

🔌 Real-time Updates

SSE (Default)

initializeNotifications({
  config: {
    apiUrl: 'http://localhost:3000',
    userId: 'user:123'
  }
});

// Uses GET /notifications/:userId/stream by default

WebSocket (Optional)

initializeNotifications({
  config: {
    apiUrl: 'http://localhost:3000',
    realtimeTransport: 'websocket',
    wsUrl: 'http://localhost:3000/notifications',
    userId: 'user:123'
  }
});

// Components automatically receive real-time updates! ✨

Backend:

import WebSocket from 'ws';

const wss = new WebSocket.Server({ port: 3001 });

wss.on('connection', (ws, req) => {
  const userId = new URL(req.url!, 'ws://localhost').searchParams.get('userId');
  
  const unsubscribe = notificationCenter.subscribe(userId!, (notification) => {
    ws.send(JSON.stringify({
      type: 'notification',
      notification
    }));
  });

  ws.on('close', () => unsubscribe());
});

Polling (Fallback)

initializeNotifications({
  config: {
    apiUrl: 'http://localhost:3000',
    realtimeTransport: 'polling',
    userId: 'user:123',
    pollInterval: 5000 // Poll every 5 seconds
  }
});

Advanced Patterns

Grouped Notifications

function GroupedNotifications() {
  const { notifications } = useNotifications();

  const grouped = notifications.reduce((acc, notif) => {
    const key = notif.category || 'other';
    if (!acc[key]) acc[key] = [];
    acc[key].push(notif);
    return acc;
  }, {});

  return (
    <div>
      {Object.entries(grouped).map(([category, notifs]) => (
        <div key={category}>
          <h3>{category} ({notifs.length})</h3>
          <NotificationList notifications={notifs} />
        </div>
      ))}
    </div>
  );
}

Infinite Scroll

function InfiniteNotificationList() {
  const [page, setPage] = useState(0);
  const { notifications } = useNotifications({
    limit: 20,
    offset: page * 20
  });

  return (
    <InfiniteScroll
      dataLength={notifications.length}
      next={() => setPage(p => p + 1)}
      hasMore={true}
    >
      {notifications.map(n => <NotificationCard key={n.id} {...n} />)}
    </InfiniteScroll>
  );
}

Custom Renderers

function SmartNotificationList() {
  const { notifications } = useNotifications();

  const renderNotification = (notif) => {
    switch (notif.type) {
      case 'comment':
        return <CommentNotification {...notif} />;
      case 'like':
        return <LikeNotification {...notif} />;
      case 'security':
        return <SecurityAlert {...notif} />;
      default:
        return <DefaultNotification {...notif} />;
    }
  };

  return (
    <div>
      {notifications.map(notif => (
        <div key={notif.id}>{renderNotification(notif)}</div>
      ))}
    </div>
  );
}

⚡ Performance Tips

  1. Use Selective Hooks: useUnreadCount() only re-renders on count changes
  2. Filter Early: Pass filters to hooks instead of filtering in components
  3. Memoize: Use React.memo() for notification cards
  4. Virtualize: Use react-window for long lists
  5. Debounce: Debounce rapid state updates

🆚 Comparison with Context/Redux

Traditional Approach (Context)

// ❌ Need provider
<NotificationProvider>
  <App />
</NotificationProvider>

// ❌ Verbose
const context = useContext(NotificationContext);
const { notifications, markAsRead } = context;

react-synq-store Approach

// ✅ No provider needed!
<App />

// ✅ Simple
const { notifications, markAsRead } = useNotifications();

🧪 Testing

import { notificationStore, initializeNotifications } from 'vynemit-react';

beforeEach(() => {
  // Reset store
  notificationStore.reset();
  
  initializeNotifications({
    config: {
      apiUrl: 'http://test',
      userId: 'test-user'
    }
  });
});

test('displays notifications', () => {
  render(<NotificationList />);
  expect(screen.getByText('Test')).toBeInTheDocument();
});

🎉 That's It!

No providers, no context, no boilerplate. Just initialize once and use anywhere!

Next Steps:

vynemit-react