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:
- Simple API: Learn in 5-10 minutes
- Performance: Surgical re-renders, automatic optimization
- Flexibility: Works with all React patterns
- Power: Conditional updates, slicing, cross-tab sync
- 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.