Using React Signify with TypeScript
Overview
React Signify is built with TypeScript-first approach, providing:
- Automatic type inference: Types are automatically inferred
- Full type safety: Compile-time error detection
- Excellent IntelliSense: Auto-complete and documentation
- Zero configuration: No additional setup required
TypeScript Installation
Installing dependencies
bash
# Install TypeScript (if not already installed)
npm install -D typescript @types/react @types/react-dom
# React Signify already includes types
npm install react-signify
TypeScript config
json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
Type Safety with Signify
1. Basic Type Inference
tsx
import { signify } from "react-signify";
// ✅ Type automatically inferred
const sCount = signify(0); // Type: Signify<number>
const sMessage = signify("Hello"); // Type: Signify<string>
const sFlag = signify(true); // Type: Signify<boolean>
function Component() {
const count = sCount.use(); // Type: number
const message = sMessage.use(); // Type: string
const flag = sFlag.use(); // Type: boolean
return (
<div>
{count} - {message} - {flag}
</div>
);
}
2. Explicit Types
tsx
import { signify } from "react-signify";
// ✅ Explicit types for complex objects
interface User {
id: number;
name: string;
email: string;
profile?: {
avatar?: string;
bio?: string;
};
}
const sUser = signify<User | null>(null);
function UserProfile() {
const user = sUser.use(); // Type: User | null
return (
<div>
{user ? (
<div>
<h1>{user.name}</h1> {/* ✅ TypeScript knows user is not null here */}
<p>{user.email}</p>
{user.profile?.avatar && (
<img src={user.profile.avatar} alt="Avatar" />
)}
</div>
) : (
<p>Please login</p>
)}
</div>
);
}
3. Generic Types
tsx
import { signify } from "react-signify";
// ✅ Generic interfaces
interface ApiResponse<T> {
data: T;
status: "loading" | "success" | "error";
error?: string;
}
interface Product {
id: number;
name: string;
price: number;
}
const sProducts = signify<ApiResponse<Product[]>>({
data: [],
status: "loading",
});
// ✅ Type-safe updates
function loadProducts() {
sProducts.set((prev) => {
prev.value.status = "loading"; // ✅ Valid
// prev.value.status = 'invalid'; // ❌ TypeScript error!
});
fetchProducts()
.then((products) => {
sProducts.set((prev) => {
prev.value.data = products; // ✅ Type: Product[]
prev.value.status = "success";
});
})
.catch((error) => {
sProducts.set((prev) => {
prev.value.status = "error";
prev.value.error = error.message;
});
});
}
Advanced TypeScript Patterns
1. Discriminated Unions
tsx
import { signify } from "react-signify";
// ✅ State with discriminated unions
type LoadingState = {
type: "loading";
};
type SuccessState = {
type: "success";
data: User[];
};
type ErrorState = {
type: "error";
message: string;
};
type AsyncState = LoadingState | SuccessState | ErrorState;
const sUserList = signify<AsyncState>({ type: "loading" });
function UserList() {
const state = sUserList.use();
// ✅ TypeScript narrows types in switch
switch (state.type) {
case "loading":
return <div>Loading...</div>;
case "success":
return (
<ul>
{state.data.map(
(
user // ✅ TypeScript knows state.data exists
) => (
<li key={user.id}>{user.name}</li>
)
)}
</ul>
);
case "error":
return <div>Error: {state.message}</div>; // ✅ TypeScript knows state.message exists
}
}
2. Type-safe Slicing
tsx
import { signify } from "react-signify";
interface ShoppingCart {
items: CartItem[];
discount: number;
shipping: number;
}
interface CartItem {
id: number;
productId: number;
name: string;
price: number;
quantity: number;
}
const sCart = signify<ShoppingCart>({
items: [],
discount: 0,
shipping: 0,
});
// ✅ Type-safe slices with automatic inference
const ssItemCount = sCart.slice((cart) => cart.items.length); // Type: number
const ssSubtotal = sCart.slice((cart) =>
cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
); // Type: number
const ssTotal = sCart.slice((cart) => {
const subtotal = cart.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return subtotal - cart.discount + cart.shipping;
}); // Type: number
// ✅ Complex slice with object return
const ssCartSummary = sCart.slice((cart) => ({
itemCount: cart.items.length,
subtotal: cart.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
),
total:
cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0) -
cart.discount +
cart.shipping,
isEmpty: cart.items.length === 0,
})); // Type: { itemCount: number; subtotal: number; total: number; isEmpty: boolean }
function CartSummary() {
const summary = ssCartSummary.use();
return (
<div>
<p>Items: {summary.itemCount}</p> {/* ✅ Full IntelliSense */}
<p>Subtotal: ${summary.subtotal}</p>
<p>Total: ${summary.total}</p>
{summary.isEmpty && <p>Cart is empty</p>}
</div>
);
}
3. Action Creators with Types
tsx
import { signify } from "react-signify";
// ✅ Typed action creators
interface Todo {
id: number;
text: string;
completed: boolean;
priority: "low" | "medium" | "high";
}
const sTodos = signify<Todo[]>([]);
// ✅ Type-safe action creators
export const todoActions = {
add: (text: string, priority: Todo["priority"] = "medium") => {
sTodos.set((prev) => {
prev.value.push({
id: Date.now(),
text,
completed: false,
priority,
});
});
},
toggle: (id: number) => {
sTodos.set((prev) => {
const todo = prev.value.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
},
updatePriority: (id: number, priority: Todo["priority"]) => {
sTodos.set((prev) => {
const todo = prev.value.find((t) => t.id === id);
if (todo) {
todo.priority = priority; // ✅ Type-safe
}
});
},
remove: (id: number) => {
sTodos.set((prev) => {
prev.value = prev.value.filter((t) => t.id !== id);
});
},
};
// ✅ Usage with full type safety
function TodoApp() {
const todos = sTodos.use();
const handleAddTodo = (text: string) => {
todoActions.add(text, "high"); // ✅ IntelliSense for priority options
};
return (
<div>
{todos.map((todo) => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => todoActions.toggle(todo.id)}>
{todo.completed ? "Undo" : "Complete"}
</button>
<select
value={todo.priority}
onChange={(e) =>
todoActions.updatePriority(
todo.id,
e.target.value as Todo["priority"]
)
}
>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
))}
</div>
);
}
Configuration Types
1. Signify Configuration
tsx
import { signify, type TSignifyConfig } from "react-signify";
// ✅ Typed configuration
const config: TSignifyConfig = {
cache: {
key: "user-preferences",
type: "LocalStorage", // Optional: 'LocalStorage' | 'SesionStorage'
},
syncKey: "user-prefs-sync",
};
interface UserPreferences {
theme: "light" | "dark";
language: "en" | "vi" | "fr";
notifications: boolean;
}
const sUserPrefs = signify<UserPreferences>(
{
theme: "light",
language: "en",
notifications: true,
},
config
);
2. Cache Configuration
tsx
import { signify, type TCacheInfo } from "react-signify";
// ✅ Typed cache configuration
const cacheConfig: TCacheInfo = {
key: "api-cache",
type: "LocalStorage", // Optional: defaults to memory cache if not specified
};
const sApiData = signify([], { cache: cacheConfig });
Common Type Patterns
1. Optional Properties
tsx
import { signify } from 'react-signify';
interface UserForm {
// Required fields
email: string;
password: string;
// Optional fields
firstName?: string;
lastName?: string;
phone?: string;
}
const sUserForm = signify<UserForm>({
email: '',
password: ''
// Optional fields can be omitted in initial value
});
function UserFormComponent() {
const form = sUserForm.use();
const updateField = <K extends keyof UserForm>(
field: K,
value: UserForm[K]
) => {
sUserForm.set(prev => {
prev.value[field] = value; // ✅ Type-safe field updates
});
};
return (
<form>
<input
value={form.email}
onChange={(e) => updateField('email', e.target.value)}
/>
<input
value={form.firstName || ''} {/* ✅ Handle optional fields */}
onChange={(e) => updateField('firstName', e.target.value)}
/>
</form>
);
}
2. Enum Types
tsx
import { signify } from "react-signify";
// ✅ Enum for type safety
enum OrderStatus {
PENDING = "pending",
PROCESSING = "processing",
SHIPPED = "shipped",
DELIVERED = "delivered",
CANCELLED = "cancelled",
}
interface Order {
id: number;
status: OrderStatus;
items: OrderItem[];
total: number;
}
const sCurrentOrder = signify<Order | null>(null);
// ✅ Type-safe status updates
function updateOrderStatus(newStatus: OrderStatus) {
sCurrentOrder.set((prev) => {
if (prev.value) {
prev.value.status = newStatus; // ✅ Only valid enum values
}
});
}
function OrderStatusComponent() {
const order = sCurrentOrder.use();
if (!order) return <div>No order</div>;
return (
<div>
<p>Status: {order.status}</p>
<select
value={order.status}
onChange={(e) => updateOrderStatus(e.target.value as OrderStatus)}
>
{Object.values(OrderStatus).map((status) => (
<option key={status} value={status}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</option>
))}
</select>
</div>
);
}
3. Generic Utilities
tsx
import { signify } from "react-signify";
// ✅ Generic utility types
type AsyncData<T> = {
data: T | null;
loading: boolean;
error: string | null;
};
// ✅ Helper function with generics
function createAsyncSignify<T>(initialData: T | null = null) {
return signify<AsyncData<T>>({
data: initialData,
loading: false,
error: null,
});
}
// ✅ Usage
interface User {
id: number;
name: string;
email: string;
}
const sUsers = createAsyncSignify<User[]>([]);
const sCurrentUser = createAsyncSignify<User>();
// ✅ Type-safe async operations
async function loadUsers() {
sUsers.set((prev) => {
prev.value.loading = true;
prev.value.error = null;
});
try {
const users = await fetchUsers(); // Returns User[]
sUsers.set((prev) => {
prev.value.data = users;
prev.value.loading = false;
});
} catch (error) {
sUsers.set((prev) => {
prev.value.error = error.message;
prev.value.loading = false;
});
}
}
TypeScript Best Practices
1. Strict Type Checking
tsx
// ✅ Enable strict mode in tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
// ✅ Use strict types in code
interface StrictUser {
readonly id: number; // Immutable ID
name: string; // Required
email: string; // Required
avatar?: string; // Optional
roles: readonly string[]; // Immutable array
}
const sUser = signify<StrictUser | null>(null);
2. Type Guards
tsx
import { signify } from "react-signify";
// ✅ Type guards for runtime safety
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
"email" in value
);
}
const sUserData = signify<unknown>(null);
function UserComponent() {
const userData = sUserData.use();
if (!isUser(userData)) {
return <div>Invalid user data</div>;
}
// ✅ TypeScript knows userData is User type
return (
<div>
<h1>{userData.name}</h1>
<p>{userData.email}</p>
</div>
);
}
3. Conditional Types
tsx
import { signify } from "react-signify";
// ✅ Conditional types for advanced patterns
type SignifyState<T> = T extends string | number
? T
: T extends object
? T
: never;
type UIState = {
theme: "light" | "dark";
sidebar: boolean;
modal: string | null;
};
const sUIState = signify<SignifyState<UIState>>({
theme: "light",
sidebar: false,
modal: null,
});
// ✅ Conditional slice types
type SliceReturnType<T, K extends keyof T> = T[K];
function createFieldSlice<T, K extends keyof T>(
signify: ReturnType<typeof signify<T>>,
field: K
) {
return signify.slice((state) => state[field]) as {
use(): SliceReturnType<T, K>;
value: SliceReturnType<T, K>;
// ... other slice methods
};
}
const ssTheme = createFieldSlice(sUIState, "theme"); // Type: 'light' | 'dark'
Debugging TypeScript
1. Type Inspection
tsx
import { signify } from "react-signify";
const sData = signify({ count: 0, message: "hello" });
// ✅ Inspect types in development
type DataType = typeof sData.value; // { count: number; message: string }
type UseReturnType = ReturnType<typeof sData.use>; // { count: number; message: string }
// ✅ Use type assertion when needed
const ssSpecificSlice = sData.slice((d) => d.count) as any as {
use(): number;
value: number;
};
2. Common TypeScript Errors
tsx
// ❌ Error: Type mismatch
const sCount = signify(0);
// sCount.set('hello'); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
// ✅ Fix: Correct type
sCount.set(42);
// ❌ Error: Undefined property
const sUser = signify<{ name: string } | null>(null);
// const name = sUser.use().name; // Error: Object is possibly 'null'
// ✅ Fix: Null check
const user = sUser.use();
const name = user?.name || "Guest";
// ❌ Error: Incorrect slice type
const sItems = signify<{ id: number; name: string }[]>([]);
// const firstItem = sItems.slice(items => items[0].name); // Error: Object is possibly 'undefined'
// ✅ Fix: Safe access
const ssFirstItemName = sItems.slice((items) => items[0]?.name || "Unknown");
Performance with TypeScript
1. Type-only Imports
tsx
// ✅ Import only types to avoid runtime overhead
import type { User, UserPreferences } from "./types";
import { signify } from "react-signify";
const sUser = signify<User | null>(null);
const sPrefs = signify<UserPreferences>({} as UserPreferences);
2. Const Assertions
tsx
// ✅ Const assertions for better inference
const themes = ["light", "dark", "auto"] as const;
type Theme = (typeof themes)[number]; // 'light' | 'dark' | 'auto'
const sTheme = signify<Theme>("light");
// ✅ Object const assertion
const defaultUser = {
name: "",
email: "",
role: "user",
} as const;
type UserRole = typeof defaultUser.role; // 'user'
Summary
TypeScript with React Signify provides:
- Automatic type inference: No need to declare types for most cases
- Compile-time safety: Catch errors before runtime
- Excellent developer experience: IntelliSense, auto-complete
- Advanced patterns: Generics, discriminated unions, conditional types
- Performance: Type-only imports, const assertions
Key takeaways:
- ✅ Use explicit types for complex objects
- ✅ Leverage slice types for computed values
- ✅ Type guards for runtime safety
- ✅ Strict TypeScript configuration
- ✅ Proper null/undefined handling
Next, explore API Reference to learn about all methods and options.
💡 Tip: Start with basic types, then gradually use advanced patterns when needed.