Understanding the .Wrap
Component
What is the Wrap Component?
The .Wrap
component is a special React component that comes with every Signify instance. It uses the render prop pattern (also known as "function as children") to give you access to the current state value and automatically re-render when that value changes.
Think of it as a "window" into your Signify state that you can place anywhere in your component tree.
Basic Concept
<signify.Wrap>
{value => <div>{value}</div>}
</signify.Wrap>
Here's what happens:
- Wrap subscribes to the Signify state
- Wrap calls your function with the current value
- Your function returns JSX to render
- When state changes, Wrap automatically calls your function again with the new value
Step-by-Step Learning
Step 1: Your First Wrap Component
Let's start with the simplest possible example:
import { signify } from 'react-signify';
// Create a Signify state with initial value of 0
const sCount = signify(0);
function MyFirstWrap() {
return (
<sCount.Wrap>
{count => <div>The count is: {count}</div>}
</sCount.Wrap>
);
}
What's happening here:
sCount.Wrap
creates a component that "watches" the count value- The function
{count => <div>The count is: {count}</div>}
is called every time the count changes - The
count
parameter contains the current value fromsCount
Step 2: Making It Interactive
Now let's add a button to change the value:
function InteractiveCounter() {
return (
<div>
<h1>My Counter</h1>
<sCount.Wrap>
{count => (
<div>
<p>Current count: {count}</p>
<button onClick={() => sCount.set(count + 1)}>
Click to increment
</button>
</div>
)}
</sCount.Wrap>
</div>
);
}
Key insight: When you click the button, sCount.set()
updates the state, which automatically triggers the Wrap to re-render with the new value!
Step 3: Working with Complex Data
Wrap isn't limited to simple values like numbers. It works great with objects too:
const sUser = signify({
name: 'John Doe',
email: 'john@example.com',
avatar: 'https://example.com/avatar.jpg'
});
function UserProfile() {
return (
<div className="user-profile">
<sUser.Wrap>
{user => (
<div className="user-card">
<img src={user.avatar} alt={`${user.name}'s avatar`} />
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={() => sUser.set(prev => {
prev.value.name = 'Jane Doe';
})}>
Change Name
</button>
</div>
)}
</sUser.Wrap>
</div>
);
}
What's new here:
- The
user
parameter is now an object with multiple properties - You can access any property:
user.name
,user.email
,user.avatar
- When any property changes, the entire Wrap re-renders automatically
Step 4: Understanding Nested Wraps
Sometimes you need data from multiple Signify states. You can nest Wrap components:
const sFirstName = signify('John');
const sLastName = signify('Doe');
const sAge = signify(25);
function UserInfo() {
return (
<div>
<sFirstName.Wrap>
{firstName => (
<sLastName.Wrap>
{lastName => (
<sAge.Wrap>
{age => (
<div>
<h1>{firstName} {lastName}</h1>
<p>Age: {age}</p>
<button onClick={() => sAge.set(age + 1)}>
Happy Birthday!
</button>
</div>
)}
</sAge.Wrap>
)}
</sLastName.Wrap>
)}
</sFirstName.Wrap>
</div>
);
}
Important note: Nested Wraps work but can get hard to read. For multiple simple values, consider using .use()
instead (we'll compare them later).
When to Use .Wrap
vs .use()
Now that you understand how Wrap works, let's see when to use it versus the .use()
hook.
The .use()
Approach
function UserProfile() {
const user = sUser.use();
return (
<div className="user-card">
<img src={user.avatar} alt={`${user.name}'s avatar`} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
The .Wrap
Approach
function UserProfile() {
return (
<div className="user-profile">
<sUser.Wrap>
{user => (
<div className="user-card">
<img src={user.avatar} alt={`${user.name}'s avatar`} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)}
</sUser.Wrap>
</div>
);
}
Quick Comparison
Aspect | .use() | .Wrap |
---|---|---|
Readability | ✅ Simpler, more familiar | ⚠️ Can get complex when nested |
Performance | ⚠️ Entire component re-renders | ✅ Only Wrap content re-renders |
Multiple states | ✅ Easy to use multiple | ⚠️ Requires nesting |
Conditional logic | ✅ Use normal if/else | ⚠️ Must be inside render function |
Testing | ✅ Easier to test | ⚠️ More complex testing setup |
When to Choose Each
Use .use()
when:
- You need multiple Signify values
- You have complex conditional logic
- You prefer a hook-like API
- Your component is simple
Use .Wrap
when:
- You want better performance isolation
- You're building reusable render prop components
- You only need one Signify value
- You want to avoid parent re-renders
Advanced Patterns
Once you're comfortable with the basics, here are some powerful patterns you can use with Wrap.
Pattern 1: Conditional Rendering
One of the most common patterns is showing different content based on the state value:
const sUser = signify(null); // null means no user logged in
function AuthenticatedContent() {
return (
<sUser.Wrap>
{user => (
user ? (
// User is logged in
<div>
<h1>Welcome back, {user.name}!</h1>
<UserDashboard user={user} />
</div>
) : (
// No user logged in
<div>
<h1>Please log in</h1>
<LoginForm />
</div>
)
)}
</sUser.Wrap>
);
}
Why this is useful:
- The entire component switches between login and dashboard automatically
- When
sUser.set(userObject)
is called, it instantly shows the dashboard - When
sUser.set(null)
is called, it instantly shows the login form
Pattern 2: Data Transformation and Calculations
You can perform calculations directly in the render function:
const sProducts = signify([
{ id: 1, name: 'Laptop', price: 1000 },
{ id: 2, name: 'Mouse', price: 25 },
{ id: 3, name: 'Keyboard', price: 75 }
]);
function ProductSummary() {
return (
<div>
<h2>Product Summary</h2>
<sProducts.Wrap>
{products => {
// Calculate totals from the products array
const totalProducts = products.length;
const totalValue = products.reduce((sum, p) => sum + p.price, 0);
const averagePrice = totalValue / totalProducts;
return (
<div>
<p>Total products: {totalProducts}</p>
<p>Total value: ${totalValue}</p>
<p>Average price: ${averagePrice.toFixed(2)}</p>
<h3>Product List:</h3>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
}}
</sProducts.Wrap>
</div>
);
}
Key point: All calculations update automatically when the products array changes!
Pattern 3: Loading States
A very practical pattern is handling loading, error, and success states:
const sApiState = signify({
data: null,
loading: false,
error: null
});
// Helper function to simulate API call
const loadUserData = async () => {
sApiState.set(prev => { prev.value.loading = true; prev.value.error = null; });
try {
const response = await fetch('/api/user');
const data = await response.json();
sApiState.set(prev => {
prev.value.data = data;
prev.value.loading = false;
});
} catch (error) {
sApiState.set(prev => {
prev.value.error = error.message;
prev.value.loading = false;
});
}
};
function UserDataDisplay() {
return (
<div>
<h2>User Information</h2>
<sApiState.Wrap>
{state => {
// Loading state
if (state.loading) {
return (
<div className="loading">
<p>Loading user data...</p>
</div>
);
}
// Error state
if (state.error) {
return (
<div className="error">
<p>Failed to load data: {state.error}</p>
<button onClick={loadUserData}>
Try Again
</button>
</div>
);
}
// Success state
if (state.data) {
return (
<div className="success">
<h3>Welcome, {state.data.name}!</h3>
<p>Email: {state.data.email}</p>
</div>
);
}
// Initial state
return (
<div className="initial">
<p>Click to load your data</p>
<button onClick={loadUserData}>
Load My Data
</button>
</div>
);
}}
</sApiState.Wrap>
</div>
);
}
This pattern is powerful because:
- All state transitions happen automatically
- The UI always reflects the current state
- You handle all possible states in one place
Performance Understanding
Why Wrap Can Be More Performant
Understanding when Wrap performs better than .use()
helps you make better architectural decisions.
Parent Re-render Problem
function ParentComponent() {
const [parentState, setParentState] = useState(0);
return (
<div>
<button onClick={() => setParentState(s => s + 1)}>
Update Parent: {parentState}
</button>
{/* ❌ Problem: This will re-render when parent re-renders */}
<ExpensiveUserProfile />
</div>
);
}
function ExpensiveUserProfile() {
const user = sUser.use(); // This causes entire component to re-render
return (
<div className="expensive-component">
{/* Imagine this is very expensive to render */}
<ComplexChart data={user.data} />
<HeavyCalculations user={user} />
</div>
);
}
Wrap Solution
function ParentComponent() {
const [parentState, setParentState] = useState(0);
return (
<div>
<button onClick={() => setParentState(s => s + 1)}>
Update Parent: {parentState}
</button>
{/* ✅ Better: Only the Wrap content re-renders */}
<UserProfileWrapper />
</div>
);
}
function UserProfileWrapper() {
return (
<sUser.Wrap>
{user => (
<div className="expensive-component">
<ComplexChart data={user.data} />
<HeavyCalculations user={user} />
</div>
)}
</sUser.Wrap>
);
}
Key insight: The wrapper component itself doesn't re-render when the parent changes, only when sUser
changes.
Common Pitfalls and Best Practices
❌ Avoid: Deep Nesting
// ❌ Don't do this - it's hard to read and maintain
<sUser.Wrap>
{user => (
<sSettings.Wrap>
{settings => (
<sNotifications.Wrap>
{notifications => (
<sTheme.Wrap>
{theme => (
<div className={theme}>
<h1>{user.name}</h1>
<p>Notifications: {notifications.length}</p>
<p>Language: {settings.language}</p>
</div>
)}
</sTheme.Wrap>
)}
</sNotifications.Wrap>
)}
</sSettings.Wrap>
)}
</sUser.Wrap>
✅ Better: Use .use() for Multiple Values
// ✅ Much cleaner for multiple values
function UserDashboard() {
const user = sUser.use();
const settings = sSettings.use();
const notifications = sNotifications.use();
const theme = sTheme.use();
return (
<div className={theme}>
<h1>{user.name}</h1>
<p>Notifications: {notifications.length}</p>
<p>Language: {settings.language}</p>
</div>
);
}
❌ Avoid: Expensive Operations in Render Function
// ❌ Don't do heavy calculations every render
<sLargeDataset.Wrap>
{data => {
// This runs on every render - bad performance!
const expensiveResult = data.reduce((acc, item) => {
return performComplexCalculation(acc, item);
}, {});
return <div>{JSON.stringify(expensiveResult)}</div>;
}}
</sLargeDataset.Wrap>
✅ Better: Use .slice() for Computed Values
// ✅ Calculate once, subscribe to changes
const sExpensiveResult = sLargeDataset.slice(data =>
data.reduce((acc, item) => performComplexCalculation(acc, item), {})
);
function DataDisplay() {
return (
<sExpensiveResult.Wrap>
{result => <div>{JSON.stringify(result)}</div>}
</sExpensiveResult.Wrap>
);
}
## TypeScript Support
Wrap components work seamlessly with TypeScript and provide full type safety:
```tsx
interface User {
id: number;
name: string;
preferences: {
theme: 'light' | 'dark';
language: string;
};
}
const sUser = signify<User | null>(null);
function TypeSafeUserProfile() {
return (
<div>
<sUser.Wrap>
{(user) => ( // user: User | null (automatically inferred)
user ? (
<div>
<h1>{user.name}</h1> {/* ✅ Type-safe */}
<p>Theme: {user.preferences.theme}</p> {/* ✅ IntelliSense works */}
<p>Language: {user.preferences.language}</p>
</div>
) : (
<div>No user logged in</div>
)
)}
</sUser.Wrap>
</div>
);
}
TypeScript benefits:
- Automatic type inference for the value parameter
- Full IntelliSense support in your editor
- Compile-time error checking
- Refactoring safety
Quick Reference
Basic Syntax
<signify.Wrap>
{value => <div>{value}</div>}
</signify.Wrap>
When to Use .Wrap
✅ Good for:
- Single Signify state access
- Render prop patterns
- Performance isolation from parent re-renders
- Building reusable components
❌ Avoid when:
- You need multiple Signify states (use
.use()
instead) - You have complex conditional logic before rendering
- The component is very simple (
.use()
might be cleaner)
Performance Tips
- Avoid deep nesting of multiple Wraps
- Don't do expensive calculations in the render function
- Use
.slice()
for computed values - Consider
.HardWrap
for even better performance isolation
Summary
The .Wrap
component is a powerful tool in the React Signify toolkit that provides:
Key Benefits
- Render Prop Pattern: Clean function-as-children syntax
- Performance Isolation: Only re-renders when Signify state changes, not parent
- Reactive UI: Automatically updates when state changes
- Type Safety: Full TypeScript support with automatic inference
When to Choose Wrap vs Use
Choose .Wrap
when:
- You need performance isolation from parent re-renders
- You're building reusable render prop components
- You want to encapsulate reactive logic
- You only need one Signify state
Choose .use()
when:
- You need multiple Signify states
- You have complex conditional logic
- The component is simple and straightforward
- You prefer hook-style APIs
Final Tips
- Start Simple: Begin with basic
.Wrap
usage and gradually explore advanced patterns - Avoid Deep Nesting: Use
.use()
for multiple states instead of nesting Wraps - Performance Matters: Use
.slice()
for computed values and.HardWrap
for heavy components - Type Safety: Leverage TypeScript for better development experience
The .Wrap
component bridges the gap between imperative state management and declarative React patterns, giving you the best of both worlds!
💡 Next Steps: Try the .HardWrap
component for even better performance, or explore .slice()
for computed values.