Skip to content

Understanding React Signify

Overview

React Signify is a global state management library built on the Signal Pattern - a simple yet powerful pattern for reactive programming.

Problems that Signify Solves

🤔 Issues with Traditional State Management

tsx
// ❌ Props drilling
function App() {
  const [user, setUser] = useState(null);
  
  return (
    <Header user={user} setUser={setUser} />
    <Content user={user} />
    <Sidebar user={user} />
  );
}

function Header({ user, setUser }) {
  return (
    <Navbar user={user} setUser={setUser} />
  );
}

function Navbar({ user, setUser }) {
  // Finally used here!
  return <div>{user?.name}</div>;
}
tsx
// ❌ Context API - complex with nested providers
function App() {
  return (
    <UserProvider>
      <ThemeProvider>
        <CartProvider>
          <Component /> {/* Re-renders when any context changes */}
        </CartProvider>
      </ThemeProvider>
    </UserProvider>
  );
}

✅ Solution with Signify

tsx
// ✅ Simple, direct
import { signify } from 'react-signify';

// Create global state
const sUser = signify(null);

// Use anywhere
function Navbar() {
  const user = sUser.use();
  return <div>{user?.name}</div>;
}

function UpdateButton() {
  return (
    <button onClick={() => sUser.set({ name: 'John' })}>
      Update User
    </button>
  );
}

Core Concepts

1. Signal Pattern

A Signal is a container that holds a value and automatically notifies subscribers when the value changes:

tsx
const signal = signify(initialValue);

// When value changes...
signal.set(newValue);

// ...all subscribers will be notified
signal.use(); // Component will re-render
signal.watch(value => console.log(value)); // Callback will be called

2. Reactive Updates

Signify only updates when the value actually changes:

tsx
const sCount = signify(0);

sCount.set(0); // ❌ No re-render (value unchanged)
sCount.set(1); // ✅ Re-render (value changed: 0 → 1)
sCount.set(1); // ❌ No re-render (value unchanged)

3. Selective Subscriptions

Only components that actually use the signify will re-render:

tsx
const sUser = signify({ name: 'John', age: 25 });

function ComponentA() {
  const user = sUser.use(); // ✅ Will re-render when sUser changes
  return <div>{user.name}</div>;
}

function ComponentB() {
  // ❌ Won't re-render when sUser changes (not subscribed)
  return <div>Static content</div>;
}

How It Works in Detail

Architecture Overview

┌─────────────────┐
│   Component A   │─ ─ ─ ┐
└─────────────────┘      │
                         │          ┌─────────────────┐
┌─────────────────┐      ├ ─ ─ ─ ─ ─│     Signify     │
│   Component B   │─ ─ ─ ┤          │   (State + ID)  │
└─────────────────┘      │          └─────────────────┘
                         │                   │
┌─────────────────┐      │                   │
│   Component C   │─ ─ ─ ┘                   ▼
└─────────────────┘                 ┌─────────────────┐
                                    │    Listeners    │
                                    │      (Set)      │
                                    └─────────────────┘

Subscription Lifecycle

tsx
function MyComponent() {
  // 1. Component mount → Subscribe to signify
  const value = sData.use();
  
  // 2. Signify changes → Component re-renders
  // 3. Component unmount → Unsubscribe automatically
  
  return <div>{value}</div>;
}

Memory Management

Signify automatically manages memory:

tsx
function MyComponent() {
  const value = sData.use();
  
  // When component unmounts:
  // ✅ Listener automatically cleaned up
  // ✅ No memory leaks
  // ✅ Signify value is preserved for other components
  
  return <div>{value}</div>;
}

Unique Features

1. Conditional Rendering & Updates

tsx
const sScore = signify(0);

// Only update when condition is met
sScore.conditionUpdating((prev, curr) => curr >= prev);

// Only render when condition is met  
sScore.conditionRendering(value => value % 10 === 0);

2. Built-in Slicing

tsx
const sUser = signify({ name: 'John', profile: { age: 25, city: 'HN' } });

// Create slice that only tracks name
const ssUserName = sUser.slice(user => user.name);

// Component only re-renders when name changes, not age/city
function UserName() {
  const name = ssUserName.use();
  return <span>{name}</span>;
}

3. Performance Wrappers

tsx
// Wrap: Affected by parent re-render
<sUser.Wrap>
  {user => <UserCard user={user} />}
</sUser.Wrap>

// HardWrap: Not affected by parent re-render
<sUser.HardWrap>
  {user => <UserCard user={user} />}
</sUser.HardWrap>

4. Cross-tab Synchronization

tsx
const sCart = signify([], { 
  syncKey: 'shopping-cart' // Sync between tabs
});

// Add product in tab 1
sCart.set(prev => {
  prev.value.push(newItem);
});

// Tab 2 automatically updates!

Best Practices

1. Naming Convention

tsx
// ✅ Prefix "s" for signify
const sUser = signify(null);
const sTheme = signify('light');

// ✅ Prefix "ss" for slice
const ssUserName = sUser.slice(u => u?.name);
const ssIsLoggedIn = sUser.slice(u => !!u);

2. State Structure

tsx
// ✅ Flat structure
const sUser = signify(null);
const sCart = signify([]);
const sTheme = signify('light');

// ❌ Nested structure (hard to optimize)
const sApp = signify({
  user: null,
  cart: [],
  theme: 'light'
});

3. Component Patterns

tsx
// ✅ Use signify directly in component
function UserProfile() {
  const user = sUser.use();
  return <div>{user?.name}</div>;
}

// ✅ Use slice for specific data
function UserName() {
  const name = ssUserName.use();
  return <span>{name}</span>;
}

// ✅ Use HardWrap for performance
function ExpensiveComponent() {
  return (
    <sUser.HardWrap>
      {user => <ComplexUserCard user={user} />}
    </sUser.HardWrap>
  );
}

When to Use Signify?

✅ Suitable for:

  • SPAs with complex state: User session, shopping cart, themes
  • Real-time applications: Chat, notifications, live data
  • Performance-critical apps: Apps needing re-render optimization
  • Cross-component communication: Components need to share data
  • Rapid prototyping: Need fast setup without complexity

❌ Not necessary for:

  • Static sites: No dynamic state
  • Simple forms: Local state is sufficient
  • Server-side only: No client state
  • Existing Redux projects: Unless planning to migrate

Summary

React Signify provides:

  1. Simple API: Learn in 5-10 minutes
  2. Performance: Surgical re-renders, automatic optimization
  3. Flexibility: Works with all React patterns
  4. Power: Conditional updates, slicing, cross-tab sync
  5. Developer Experience: TypeScript, DevTools, debugging

Next, learn about Project Structure to know how to organize code effectively.


💡 Tip: Start with a simple signify, then gradually explore advanced features when needed.

Released under the MIT License.