Understanding the .slice()
Method
What is Slice?
The .slice()
method is one of the most powerful features in React Signify. It allows you to create a derived signify that focuses on just a specific part of your original state. Think of it like creating a "window" that only shows the data you care about.
const ssSlice = signify.slice(pickFunction);
Why is this useful?
- 🚀 Performance: Your components only re-render when the specific data they need actually changes
- 🎯 Focus: Each component gets exactly the data it needs, nothing more
- 💡 Computed Values: You can create calculated values that automatically update when their dependencies change
The Problem Slice Solves
Before we dive into how slice works, let's understand the problem it solves:
// ❌ Problem: Without slice
const sAppState = signify({
user: { name: "John", email: "john@example.com" },
cart: [{ id: 1, name: "Item 1" }],
theme: "light",
notifications: [],
});
function UserName() {
const appState = sAppState.use(); // Re-renders when ANYTHING changes!
return <h1>{appState.user.name}</h1>; // Even when cart, theme, or notifications change
}
In this example, the UserName
component will re-render whenever any part of sAppState
changes, even though it only cares about the user's name.
// ✅ Solution: With slice
const ssUserName = sAppState.slice((state) => state.user.name);
function UserName() {
const name = ssUserName.use(); // Only re-renders when user.name changes!
return <h1>{name}</h1>;
}
Now the component only re-renders when the user's name actually changes!
Basic Usage Examples
1. Simple Property Access
import { signify } from "react-signify";
const sUser = signify({
id: 1,
name: "John Doe",
email: "john@example.com",
profile: {
age: 25,
city: "New York",
},
});
// Create slices for specific properties
const ssUserName = sUser.slice((user) => user.name);
const ssUserEmail = sUser.slice((user) => user.email);
function UserName() {
const name = ssUserName.use(); // Only re-renders when name changes
return <h1>Hello, {name}!</h1>;
}
function UserEmail() {
const email = ssUserEmail.use(); // Only re-renders when email changes
return <p>Contact: {email}</p>;
}
2. Nested Property Access
// Accessing deeply nested properties
const ssUserAge = sUser.slice((user) => user.profile.age);
const ssUserCity = sUser.slice((user) => user.profile.city);
function UserInfo() {
const age = ssUserAge.use(); // Only re-renders when age changes
const city = ssUserCity.use(); // Only re-renders when city changes
return (
<div>
<p>Age: {age}</p>
<p>City: {city}</p>
</div>
);
}
// Safe nested access with null checking
const ssUserBirthYear = sUser.slice((user) => {
if (!user.profile?.age) return "Unknown";
return new Date().getFullYear() - user.profile.age;
});
3. Creating Computed Values
One of the most powerful features of slice is creating computed values - values that are automatically calculated from your state and only recalculated when their dependencies change.
const sCart = signify([
{ id: 1, price: 100, quantity: 2 },
{ id: 2, price: 25, quantity: 1 }
]);
// Computed slices - automatically calculated
const ssItemCount = sCart.slice(items => items.length);
const ssTotal = sCart.slice(items =>
items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
function CartSummary() {
const itemCount = ssItemCount.use(); // Re-renders when count changes
const total = ssTotal.use(); // Re-renders when total changes
return (
<div>
<p>Items: {itemCount}</p>
<p>Total: ${total}</p>
</div>
);
}
What's happening here?
- Each computed slice only recalculates when its specific dependencies change
- Adding a new item triggers updates to both
ssItemCount
andssTotal
- Changing just the quantity updates
ssTotal
, but notssItemCount
- This selective updating is much more efficient than recalculating everything on every change
What You Can Do with Slices
A slice behaves very similarly to a regular signify, but with some important differences. Let's explore what's available and what's not.
✅ Available Methods and Properties
Since a slice is a derived value, you can read from it and watch it, but you cannot modify it directly:
const sUser = signify({ name: 'John', age: 25, city: 'New York' });
const ssUserName = sUser.slice(user => user.name);
// 1. Reading the current value
const currentName = ssUserName.value; // 'John'
// 2. Using in React components (with reactivity)
const name = ssUserName.use(); // Subscribe to changes
// 3. Watching for changes outside React
ssUserName.watch(name => {
console.log('Name changed to:', name);
});
// 4. Rendering as HTML string
const nameHtml = ssUserName.html; // '<span>John</span>'
// 5. Using wrapper components
<ssUserName.Wrap>
{name => <div className="user-name">{name}</div>}
</ssUserName.Wrap>
<ssUserName.HardWrap>
{name => <div className="user-name">{name}</div>}
</ssUserName.HardWrap>
// 6. Controlling when the slice updates
ssUserName.conditionRendering(name => name.length > 0); // Only update if name isn't empty
// 7. Pausing and resuming updates
ssUserName.stop(); // Temporarily stop receiving updates
ssUserName.resume(); // Resume updates
// 8. Development tools
<ssUserName.DevTool /> // Debug component for development
❌ What You Cannot Do with Slices
Since slices are derived from the original signify, you cannot modify them directly:
// ❌ Cannot set slice values directly
// ssUserName.set('Jane'); // Error! Slices are read-only
// ❌ Cannot reset slices
// ssUserName.reset(); // Error! Only the original signify can be reset
// ❌ Cannot configure slice updating conditions
// ssUserName.conditionUpdating(...); // Error! This is only for the source signify
To change a slice's value, you must change the original signify:
// ✅ Correct way: change the original signify
sUser.set((prev) => {
prev.value.name = "Jane"; // This will automatically update ssUserName
});
// ✅ Or use the simpler syntax
sUser.set({ name: "Jane", age: 25, city: "New York" });
Why Slice Improves Performance
Understanding the performance benefits of slice is crucial for building efficient React applications. Let's explore the key advantages:
1. Prevents Unnecessary Re-renders
Without slice, components re-render whenever any part of the state changes, even if they don't care about those changes:
const sAppState = signify({
user: { name: "John", email: "john@example.com" },
cart: [{ id: 1, name: "Laptop", price: 1000 }],
theme: "light",
notifications: [],
sidebar: { isOpen: false },
modal: { isVisible: false },
});
// ❌ PROBLEM: Over-rendering
function UserGreeting() {
const appState = sAppState.use(); // Subscribes to ALL changes!
return <h1>Hello, {appState.user.name}!</h1>;
}
// This component will re-render when:
// - Cart items change
// - Theme changes
// - Notifications are added
// - Sidebar opens/closes
// - Modal opens/closes
// Even though it only needs the user's name!
With slice, you can surgically subscribe to only the data you need:
// ✅ SOLUTION: Precise subscriptions
const ssUserName = sAppState.slice((state) => state.user.name);
function UserGreeting() {
const userName = ssUserName.use(); // Only subscribes to user.name changes!
return <h1>Hello, {userName}!</h1>;
}
// This component now ONLY re-renders when user.name actually changes
// Changes to cart, theme, notifications, etc. are ignored
2. Caches Expensive Computations
Without slice, expensive calculations are performed on every render:
const sTransactions = signify([
{ amount: 100, type: "income" },
{ amount: 50, type: "expense" }
]);
// ❌ PROBLEM: Recalculating on every render
function FinancialSummary() {
const transactions = sTransactions.use();
// This expensive calculation runs on EVERY render!
const total = transactions.reduce((sum, t) =>
sum + (t.type === "income" ? t.amount : -t.amount), 0
);
return <div>Net: ${total}</div>;
}
With slice, the calculation is cached and only runs when data actually changes:
// ✅ SOLUTION: Cached computations
const ssTotal = sTransactions.slice(transactions =>
// This calculation only runs when transactions actually change!
transactions.reduce((sum, t) =>
sum + (t.type === "income" ? t.amount : -t.amount), 0
)
);
function FinancialSummary() {
const total = ssTotal.use(); // Gets cached result!
return <div>Net: ${total}</div>;
}
3. Performance Comparison
Here's what happens in a typical app:
// Without slice: Component subscribes to everything
const BadDashboard = () => {
const appState = sAppState.use(); // 😱 Re-renders on ANY change
return <div>User: {appState.user.name}</div>;
};
// With slice: Component subscribes precisely
const GoodDashboard = () => {
const userName = ssUserName.use(); // 🚀 Re-renders only when needed
return <div>User: {userName}</div>;
};
// Performance difference:
// - Bad: Re-renders 50+ times per minute in a typical app
// - Good: Re-renders only when user.name actually changes (maybe 1-2 times per session)
Real-world impact:
- ✅ Smoother animations and interactions
- ✅ Better battery life on mobile devices
- ✅ Improved user experience, especially on slower devices
- ✅ Easier debugging (fewer unnecessary renders to track)
Advanced Slice Patterns
Once you understand the basics of slice, you can use these advanced patterns to build more sophisticated applications.
1. Safe Conditional Slicing
When working with data that might be null or undefined, you can use slice to provide safe defaults:
const sUser = signify({
user: null,
permissions: []
});
// Safe slicing with fallback values
const ssUserName = sUser.slice(data => data.user?.name || "Guest");
const ssCanEdit = sUser.slice(data => data.permissions.includes("admin"));
function UserHeader() {
const userName = ssUserName.use(); // Always safe, never crashes
const canEdit = ssCanEdit.use(); // true/false based on permissions
return (
<div>
<h1>Welcome, {userName}!</h1>
{canEdit && <button>Edit Content</button>}
</div>
);
}
// When user logs in:
sUser.set(prev => {
prev.value.user = { name: "John Doe" };
prev.value.permissions = ["admin"];
});
2. Complex Object Transformations
Sometimes you need to combine data from multiple parts of your state into a new structure. Slice is perfect for this:
const sOrder = signify({
items: [{ price: 100, quantity: 2 }, { price: 50, quantity: 1 }],
customer: { name: "Sarah", isPremium: true },
tax: 0.1
});
// Combine data from multiple parts into new structure
const ssOrderSummary = sOrder.slice(data => {
const subtotal = data.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const tax = subtotal * data.tax;
return {
customer: data.customer.name,
isPremium: data.customer.isPremium,
subtotal,
tax,
total: subtotal + tax
};
});
function OrderCard() {
const summary = ssOrderSummary.use();
return (
<div>
<h3>Order for {summary.customer}</h3>
{summary.isPremium && <span>Premium Member</span>}
<p>Subtotal: ${summary.subtotal}</p>
<p>Tax: ${summary.tax}</p>
<p><strong>Total: ${summary.total}</strong></p>
</div>
);
}
What makes this powerful:
- The slice combines data from
items
,customer
, andtax
into one object - All calculations are cached - they only run when the source data changes
- The component gets a clean, ready-to-use summary object
- Adding a new item automatically recalculates the totals
3. Array Filtering and Sorting
One of the most common use cases for slice is efficiently filtering and sorting lists. Here's how to do it right:
const sTasks = signify([
{ id: 1, title: "Task A", completed: false, priority: "high" },
{ id: 2, title: "Task B", completed: true, priority: "low" }
]);
const sFilter = signify({ status: "all", priority: "all" });
// Step 1: Filter tasks
const ssFilteredTasks = signify.slice(() => {
const tasks = sTasks.value;
const filter = sFilter.value;
return tasks.filter(task => {
const statusMatch = filter.status === "all" ||
(filter.status === "pending" && !task.completed) ||
(filter.status === "completed" && task.completed);
const priorityMatch = filter.priority === "all" || task.priority === filter.priority;
return statusMatch && priorityMatch;
});
});
// Step 2: Sort filtered tasks
const ssSortedTasks = ssFilteredTasks.slice(tasks =>
[...tasks].sort((a, b) => a.title.localeCompare(b.title))
);
function TaskList() {
const tasks = ssSortedTasks.use(); // Only updates when filtered/sorted results change
return (
<div>
<div>
<select
value={sFilter.value.status}
onChange={(e) => sFilter.set(prev => { prev.value.status = e.target.value; })}
>
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="completed">Completed</option>
</select>
</div>
<div>
{tasks.map(task => (
<div key={task.id}>
<span>{task.title} - {task.priority}</span>
<span>{task.completed ? "✓" : "○"}</span>
</div>
))}
</div>
</div>
);
}
Performance Benefits of This Approach:
- ✅ Filtering only happens when tasks or filters change
- ✅ Sorting only happens when filtered results or sort options change
- ✅ Statistics only recalculate when the task list changes
- ✅ Unique assignees only recalculate when assignees change
- ✅ UI components only re-render when their specific data changes
Traditional approach problems:
- ❌ All calculations happen on every render
- ❌ All components re-render when any filter changes
- ❌ No caching of expensive operations
- ❌ Poor performance with large lists
4. Slice Chaining for Complex Transformations
You can create slices from other slices to build complex data transformations. This creates a dependency chain where each slice only updates when its specific input changes.
const sEmployees = signify([
{ id: 1, name: "Alice", department: "Engineering", salary: 100000, active: true },
{ id: 2, name: "Bob", department: "Marketing", salary: 80000, active: true },
{ id: 3, name: "Charlie", department: "Engineering", salary: 90000, active: false }
]);
// Step 1: Get active employees only
const ssActiveEmployees = sEmployees.slice(employees =>
employees.filter(emp => emp.active)
);
// Step 2: Filter by department (chaining from previous slice)
const ssEngineers = ssActiveEmployees.slice(employees =>
employees.filter(emp => emp.department === "Engineering")
);
// Step 3: Calculate team stats (chaining again)
const ssEngStats = ssEngineers.slice(engineers => ({
count: engineers.length,
avgSalary: engineers.reduce((sum, eng) => sum + eng.salary, 0) / engineers.length
}));
function EngTeamDashboard() {
const activeEmployees = ssActiveEmployees.use();
const engStats = ssEngStats.use();
return (
<div>
<h2>Active Employees: {activeEmployees.length}</h2>
<h3>Engineering Team</h3>
<p>Engineers: {engStats.count}</p>
<p>Average Salary: ${engStats.avgSalary.toLocaleString()}</p>
</div>
);
}
How Slice Chaining Works:
- ssActiveEmployees → Only updates when employee active status changes
- ssEngineers → Only updates when active employees change AND some are engineering
- ssEngStats → Only updates when the engineering team membership or data changes
Performance Benefits:
- ✅ Each slice only recalculates when its specific inputs change
- ✅ Adding a non-engineering employee doesn't trigger engineering stats recalculation
- ✅ Changing an inactive employee's data doesn't affect any active employee calculations
- ✅ Complex dependency chains are automatically managed and optimized
Using Slice with Forms
Forms are a perfect use case for slice because different parts of your form often need different pieces of data, and you want validation to be efficient.
1. Smart Form Field Management
const sForm = signify({
name: '',
email: '',
password: ''
});
// Field-specific slices
const ssName = sForm.slice(form => form.name);
const ssEmail = sForm.slice(form => form.email);
// Validation slices
const ssEmailValid = sForm.slice(form =>
form.email.includes('@') && form.email.length > 5
);
function ContactForm() {
const name = ssName.use();
const email = ssEmail.use();
const emailValid = ssEmailValid.use();
return (
<form>
<input
value={name}
onChange={(e) => sForm.set(prev => { prev.value.name = e.target.value; })}
/>
<input
value={email}
className={emailValid ? 'valid' : 'invalid'}
onChange={(e) => sForm.set(prev => { prev.value.email = e.target.value; })}
/>
</form>
);
}
Best Practices for Using Slice
Understanding these best practices will help you get the most out of slice and avoid common pitfalls:
✅ DO: Use Slice for These Scenarios
// ✅ Computed values that depend on multiple pieces of data
const ssOrderTotal = sCart.slice(cart =>
cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
// ✅ Nested property access to avoid deep subscriptions
const ssUserName = sUserData.slice(data => data.user?.name || 'Guest');
// ✅ Expensive transformations that should be cached
const ssSearchResults = sProductList.slice(products =>
products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()))
);
❌ DON'T: Avoid These Anti-patterns
// ❌ Don't slice simple values unnecessarily
const ssMessage = sSimpleString.slice(msg => msg); // Pointless wrapper
// ❌ Don't perform side effects in slice functions
const ssBadSlice = sData.slice(data => {
console.log(data); // Side effect!
localStorage.setItem('data', JSON.stringify(data)); // Side effect!
return data.processed;
});
// ❌ Don't create slices inside component render
function BadComponent({ userId }) {
// This creates a new slice on every render!
const ssDynamicSlice = sUsers.slice(users => users.find(u => u.id === userId));
return <div>{ssDynamicSlice.use()?.name}</div>;
}
🎯 When to Choose Slice vs Direct Usage
Scenario | Use Slice | Use Direct |
---|---|---|
Simple property access | Only if nested deeply | ✅ Direct usage |
Computed/derived values | ✅ Always use slice | ❌ |
Expensive calculations | ✅ Always use slice | ❌ |
Multiple components need same data | ✅ Use slice | ❌ |
Filtering/sorting arrays | ✅ Use slice | ❌ |
Form validation | ✅ Use slice | ❌ |
Direct input binding | ❌ | ✅ Direct usage |
Simple boolean flags | ❌ | ✅ Direct usage |
TypeScript Support
Slice works seamlessly with TypeScript, providing full type safety and automatic type inference:
interface User {
id: number;
name: string;
profile: {
age: number;
preferences: {
theme: 'light' | 'dark';
language: string;
};
};
}
const sUser = signify<User | null>(null);
// ✅ Type-safe slices with automatic inference
const ssUserName = sUser.slice(user => user?.name || 'Guest');
// Type: string
const ssUserAge = sUser.slice(user => user?.profile.age || 0);
// Type: number
const ssUserTheme = sUser.slice(user => user?.profile.preferences.theme || 'light');
// Type: 'light' | 'dark'
TypeScript Benefits:
- ✅ Automatic type inference for slice return values
- ✅ Compile-time error detection
- ✅ IntelliSense support in editors
- ✅ Refactoring safety across your codebase
Summary
The .slice()
method is a powerful tool for building efficient, maintainable React applications with React Signify.
Key Concepts
- Derived State: Slices create derived values that automatically update when their source data changes
- Selective Updates: Components only re-render when their specific slice data changes, not when unrelated data changes
- Caching: Expensive computations are cached and only recalculated when necessary
- Type Safety: Full TypeScript support with automatic type inference
When to Use Slice
✅ Perfect for:
- Computed/derived values
- Nested property access
- Expensive transformations
- Filtering and sorting arrays
- Form validation
- Complex object transformations
- Performance optimization
❌ Avoid for:
- Simple property access (unless deeply nested)
- Direct input value binding
- Simple boolean flags
- Values that don't need computation
Performance Benefits
- 🚀 Prevents over-rendering: Components only update when relevant data changes
- 💾 Caches computations: Expensive calculations run only when needed
- 🎯 Precise subscriptions: Subscribe to exactly the data you need
- 🔧 Optimizes automatically: No manual optimization required
Best Practices Recap
- Create slices outside components - Define them at module level
- Keep slice functions pure - No side effects, always return new data
- Handle edge cases - Use optional chaining and provide fallbacks
- Use meaningful names - Make slice purpose clear from the name
- Chain slices for complex logic - Build dependencies between slices
- Test slice behavior - Verify updates happen only when expected
Real-World Impact
Using slice properly leads to:
- ✅ Smoother user interfaces
- ✅ Better performance on slower devices
- ✅ Easier debugging and maintenance
- ✅ More predictable component behavior
- ✅ Cleaner, more organized code
💡 Next Steps: Now that you understand slice, explore other React Signify features like .watch()
for side effects, .reset()
for state management, and .wrap()
for advanced component patterns.
Remember: Slice is your tool for creating efficient, focused components that only care about the data they actually need!