Using Method .use()
Overview
Method .use()
is the primary way to subscribe to Signify in React components. When the value changes, the component will automatically re-render with the new value. The method uses deep comparison to optimize re-renders, ensuring components only update when the actual data changes.
const value = signify.use(pickFunction?);
How It Works
The .use()
method provides:
- Automatic Re-renders: Your component updates when the state changes
- Smart Subscriptions: Automatically connects and disconnects from state updates
- Efficient Updates: Only re-renders when the value actually changes
- Clean Memory Usage: No memory leaks - everything is cleaned up automatically
- Optimized Performance: When using picker functions, only watches specific parts of your state
Basic Usage
import { signify } from "react-signify";
const sCount = signify(0);
function Counter() {
const count = sCount.use(); // Subscribe and get value
// ✅ Component will re-render when sCount changes
// ✅ When component unmounts:
// - Subscription automatically cleaned up
// - No memory leaks
// - Signify value still exists for other components
return <div>Count: {count}</div>;
}
Picker Function
1. Transform Value
const sUser = signify({
firstName: "John",
lastName: "Doe",
profile: { age: 25, city: "Hanoi" },
});
function UserComponent() {
// ✅ Get entire object
const user = sUser.use();
// ✅ Only get firstName
const firstName = sUser.use((user) => user.firstName);
// ✅ Computed value
const fullName = sUser.use((user) => `${user.firstName} ${user.lastName}`);
// ✅ Nested property
const age = sUser.use((user) => user.profile.age);
return (
<div>
<h1>{fullName}</h1>
<p>Age: {age}</p>
<p>City: {user.profile.city}</p>
</div>
);
}
2. Performance Optimization
const sLargeObject = signify({
data: {
/* large object */
},
metadata: {
/* another large object */
},
settings: { theme: "dark", language: "vi" },
});
function SettingsComponent() {
// ✅ Only subscribe to settings
const settings = sLargeObject.use((obj) => obj.settings);
// Component only re-renders when settings change
// DOES NOT re-render when data or metadata changes
return (
<div>
<p>Theme: {settings.theme}</p>
<p>Language: {settings.language}</p>
</div>
);
}
3. Conditional Returns
const sApiResponse = signify({ data: null, loading: true, error: null });
function DataComponent() {
// ✅ Transform API state
const status = sApiResponse.use((response) => {
if (response.loading) return "loading";
if (response.error) return "error";
if (response.data) return "success";
return "idle";
});
const data = sApiResponse.use((response) => response.data);
const error = sApiResponse.use((response) => response.error);
switch (status) {
case "loading":
return <div>Loading...</div>;
case "error":
return <div>Error: {error}</div>;
case "success":
return <div>Data: {JSON.stringify(data)}</div>;
default:
return <div>No data</div>;
}
}
Comparison with Alternatives
vs .value
Property
const sCount = signify(0);
function Component() {
// ❌ Using .value - NO re-render
const count1 = sCount.value; // Static value, no subscription
// ✅ Using .use() - Automatic re-render
const count2 = sCount.use(); // Subscribe to changes
return (
<div>
<p>Static: {count1}</p> {/* Doesn't update when signify changes */}
<p>Reactive: {count2}</p> {/* Automatically updates */}
</div>
);
}
vs React useState
import { useState } from "react";
// ❌ React useState - Local component state only
function CounterWithUseState() {
const [count, setCount] = useState(0);
// State is isolated to this component
// Other components cannot access or modify this state
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
// ✅ Signify .use() - Global reactive state
const sCount = signify(0);
function CounterWithSignify() {
const count = sCount.use();
// State is shared across all components using sCount
// Any component can modify: sCount.set(newValue)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => sCount.set(count + 1)}>+</button>
</div>
);
}
vs .Wrap
Component
const sUser = signify({ name: "John", age: 25 });
// ✅ Using .use() - Simple, direct
function UserProfile1() {
const user = sUser.use();
return (
<div>
<h1>{user.name}</h1>
<p>Age: {user.age}</p>
</div>
);
}
// ✅ Using .Wrap - Function as children
function UserProfile2() {
return (
<sUser.Wrap>
{(user) => (
<div>
<h1>{user.name}</h1>
<p>Age: {user.age}</p>
</div>
)}
</sUser.Wrap>
);
}
Advanced Patterns
1. Multiple Subscriptions
const sUser = signify({ name: "John", profile: { age: 25 } });
function UserDashboard() {
// ✅ Multiple subscriptions in one component
const userName = sUser.use((user) => user.name);
const userAge = sUser.use((user) => user.profile.age);
const isAdult = sUser.use((user) => user.profile.age >= 18);
return (
<div>
<h1>Welcome, {userName}!</h1>
<p>Age: {userAge}</p>
{isAdult && <p>You can vote!</p>}
</div>
);
}
2. Derived State Pattern
const sItems = signify([]);
function ItemsList() {
// ✅ Derived states with .use()
const items = sItems.use();
const itemCount = sItems.use((items) => items.length);
const hasItems = sItems.use((items) => items.length > 0);
const totalValue = sItems.use((items) =>
items.reduce((sum, item) => sum + item.price, 0)
);
return (
<div>
<h2>Items ({itemCount})</h2>
{hasItems ? (
<div>
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name} - ${item.price}
</li>
))}
</ul>
<p>Total: ${totalValue}</p>
</div>
) : (
<p>No items found</p>
)}
</div>
);
}
Performance Considerations
1. Deep Comparison Behavior
The .use()
method uses deep comparison to determine if a re-render is needed:
const sData = signify({ count: 0, user: { name: "John" } });
function ExampleComponent() {
// ✅ Only re-renders when count actually changes
const count = sData.use((data) => data.count);
// ✅ Only re-renders when user object content changes
const user = sData.use((data) => data.user);
// ✅ Even if the user object reference changes but content is same,
// no re-render occurs due to deep comparison
return (
<div>
{count} - {user.name}
</div>
);
}
2. Avoiding Unnecessary Re-renders
const sGlobalState = signify({
user: { name: "John" },
cart: [],
theme: "light",
notifications: [],
});
// ❌ Entire component re-renders when any part changes
function BadComponent() {
const state = sGlobalState.use();
return (
<div>
<header>User: {state.user.name}</header>
<main>Cart items: {state.cart.length}</main>
</div>
);
}
// ✅ Only re-renders when specific data changes
function GoodComponent() {
const userName = sGlobalState.use((state) => state.user.name);
const cartCount = sGlobalState.use((state) => state.cart.length);
return (
<div>
<header>User: {userName}</header>
{/* Only re-renders when user.name changes */}
<main>Cart items: {cartCount}</main>{" "}
{/* Only re-renders when cart.length changes */}
</div>
);
}
Common Patterns
1. Form Handling
const sForm = signify({
email: "",
password: "",
errors: {},
});
function LoginForm() {
const email = sForm.use((form) => form.email);
const password = sForm.use((form) => form.password);
const errors = sForm.use((form) => form.errors);
const updateEmail = (value) => {
sForm.set((prev) => {
prev.value.email = value;
// Clear error when user types
delete prev.value.errors.email;
});
};
return (
<form>
<input
type="email"
value={email}
onChange={(e) => updateEmail(e.target.value)}
/>
{errors.email && <span>{errors.email}</span>}
<input
type="password"
value={password}
onChange={(e) =>
sForm.set((prev) => {
prev.value.password = e.target.value;
})
}
/>
</form>
);
}
2. List Management
const sTodos = signify([]);
function TodoList() {
const todos = sTodos.use();
const completedCount = sTodos.use(
(todos) => todos.filter((t) => t.completed).length
);
const activeCount = sTodos.use(
(todos) => todos.filter((t) => !t.completed).length
);
return (
<div>
<h2>Todos</h2>
<p>
Active: {activeCount}, Completed: {completedCount}
</p>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
>
{todo.text}
</span>
</li>
))}
</ul>
</div>
);
}
3. Async Data Loading
import { useEffect } from "react";
const sAsyncData = signify({
data: null,
loading: false,
error: null,
});
function DataDisplay() {
const data = sAsyncData.use((state) => state.data);
const loading = sAsyncData.use((state) => state.loading);
const error = sAsyncData.use((state) => state.error);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
sAsyncData.set((prev) => {
prev.value.loading = true;
prev.value.error = null;
});
try {
const result = await fetchData();
sAsyncData.set((prev) => {
prev.value.data = result;
prev.value.loading = false;
});
} catch (err) {
sAsyncData.set((prev) => {
prev.value.error = err.message;
prev.value.loading = false;
});
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return <div>No data</div>;
return <div>Data: {JSON.stringify(data)}</div>;
}
Best Practices
✅ Do's
// ✅ Use picker function for specific data
const userName = sUser.use((user) => user.name);
// ✅ Multiple specific subscriptions instead of one large subscription
const email = sUser.use((user) => user.email);
const age = sUser.use((user) => user.profile.age);
// ✅ Transform data directly in picker
const fullName = sUser.use((user) => `${user.firstName} ${user.lastName}`);
// ✅ Conditional logic in picker
const canVote = sUser.use((user) => user.age >= 18);
❌ Don'ts
// ❌ Don't use .value in component
const count = sCount.value; // No re-render!
// ❌ Don't subscribe to entire large object if you only need a part
const largeState = sLargeState.use(); // Inefficient
// ❌ Don't perform expensive operations in picker without caching
const expensiveResult = sData.use((data) => {
return heavyComputation(data); // Re-computes every render
});
// ❌ Don't mutate returned value
const user = sUser.use();
user.name = "Modified"; // Dangerous!
TypeScript Support
The .use()
method provides full TypeScript support with generic type inference:
interface User {
id: number;
name: string;
profile: {
age: number;
avatar?: string;
};
}
const sUser = signify<User | null>(null);
function TypedComponent() {
// ✅ Full type inference
const user = sUser.use(); // Type: User | null
const userName = sUser.use((u) => u?.name); // Type: string | undefined
const userAge = sUser.use((u) => u?.profile.age); // Type: number | undefined
// ✅ Type-safe picker functions with explicit return type
const isAdult = sUser.use((u): boolean => {
return u ? u.profile.age >= 18 : false;
});
// ✅ Complex transformations with inferred types
const userInfo = sUser.use((u) =>
u
? {
displayName: u.name.toUpperCase(),
isAdult: u.profile.age >= 18,
hasAvatar: !!u.profile.avatar,
}
: null
); // Type: { displayName: string; isAdult: boolean; hasAvatar: boolean; } | null
return (
<div>
{user && (
<div>
<h1>{userName}</h1>
<p>Age: {userAge}</p>
{isAdult && <p>Can vote!</p>}
{userInfo && (
<p>
Display: {userInfo.displayName} | Avatar:
{userInfo.hasAvatar ? "Yes" : "No"}
</p>
)}
</div>
)}
</div>
);
}
Debugging
1. Development Monitoring
import { useEffect } from "react";
function DebuggingComponent() {
const data = sData.use();
// ✅ Log changes during development
useEffect(() => {
if (process.env.NODE_ENV === "development") {
console.log("Data changed:", data);
}
}, [data]);
return <div>{JSON.stringify(data)}</div>;
}
2. Conditional Debugging
import { useEffect } from "react";
function ConditionalDebugging() {
const count = sCount.use();
// ✅ Debug specific conditions
useEffect(() => {
if (count > 100) {
console.warn("Count is getting high:", count);
}
}, [count]);
return <div>Count: {count}</div>;
}
Summary
Method .use()
is the backbone of React Signify:
- Global State Access - Connect your component to shared state across your app
- Smart Re-rendering - Only updates when your data actually changes
- Automatic Cleanup - No memory leaks - everything is cleaned up automatically
- Selective Data - Pick only the data you need for better performance
- Full TypeScript Support - Complete type inference and type safety
Key Benefits:
- Seamless integration with React components
- Automatic lifecycle management
- Efficient updates that prevent unnecessary re-renders
- Flexible data transformation and selection
When to use .use()
:
- Need reactive data in component (vs local
useState
) - Want automatic re-render on state changes
- Need to transform/pick specific data from larger state
- Building reactive UI that shares state between components
When NOT to use .use()
:
- Only need to read value once without reactivity (use
.value
) - Want to listen to changes without triggering re-render (use
.watch()
) - Prefer function-as-children pattern (use
.Wrap
/.HardWrap
)
💡 Tip: Start with basic .use()
, then optimize with picker functions for performance. Remember that deep comparison means you can safely transform objects without worrying about reference equality.