Basic Usage
The Problem: Manual Key Management
When working with cache-based state management or data fetching libraries, you typically need to create array-based keys to identify your data:
// ❌ The old way: Manually constructing keys everywhere
useQuery({
queryKey: ['users', 'list', { page: 1 }],
queryFn: fetchUsers
});
useQuery({
queryKey: ['users', 'detail', userId],
queryFn: () => fetchUser(userId)
});
useMutation({
mutationKey: ['users', 'create'],
mutationFn: createUser
});
// Problems:
// 1. String literals are fragile and error-prone
// 2. Inconsistent structure across your app
// 3. Hard to refactor when you change key names
// 4. No autocomplete or type-safety
// 5. Function and key definitions are separatedThe Solution: Builder
With @ibnlanre/builder, you define your keys and functions together in a structured object (called a "register"), then let Builder handle key generation:
import { createBuilder } from '@ibnlanre/builder';
// ✅ Define once
const api = createBuilder({
users: {
list: (params: { page: number }) => fetchUsers(params),
detail: (id: number) => fetchUser(id),
create: (data: User) => createUser(data)
}
});
// ✅ Use anywhere with autocomplete
useQuery({
queryKey: api.users.list.$use({ page: 1 }), // ['users', 'list', { page: 1 }]
queryFn: () => api.$use.users.list({ page: 1 })
});How It Works
Builder transforms your register into a smart object with two special properties at every level:
1. $use - Dual Purpose Property
On the root node, $use gives you access to your original values:
const register = {
users: {
getName: (id: number) => `User ${id}`,
count: 42
}
};
const builder = createBuilder(register);
// Access values through the root $use
builder.$use.users.getName(10); // "User 10"
builder.$use.users.count; // 42On nested nodes, $use() generates keys following the function signature:
// If the value is a function with parameters
builder.users.getName.$use(10); // ['users', 'getName', 10]
// If the value is a primitive or object
builder.users.count.$use(); // ['users', 'count']2. $get() - Flexible Key Generation
The $get() method gives you more control, accepting arbitrary arguments:
// Get the key without following function signature
builder.users.getName.$get(); // ['users', 'getName']
builder.users.getName.$get(10, 'active'); // ['users', 'getName', 10, 'active']Step-by-Step Example
Let's build a complete example:
Step 1: Create Your Register
Define your keys and values in a nested structure:
const register = {
products: {
list: async (category?: string) => {
const url = category ? `/api/products?cat=${category}` : '/api/products';
return fetch(url).then(r => r.json());
},
detail: async (id: number) => fetch(`/api/products/${id}`).then(r => r.json()),
price: 29.99 // Can also store plain values
}
};Step 2: Create the Builder
const api = createBuilder(register, {
prefix: ['api', 'v1'] // Optional: add prefixes to all keys
});Step 3: Generate Keys
// Using $use - follows function signature
api.products.list.$use(); // ['api', 'v1', 'products', 'list']
api.products.list.$use('electronics'); // ['api', 'v1', 'products', 'list', 'electronics']
api.products.detail.$use(42); // ['api', 'v1', 'products', 'detail', 42]
// Using $get - flexible arguments
api.products.list.$get('sale', true); // ['api', 'v1', 'products', 'list', 'sale', true]Step 4: Access Values
// Call the actual functions
await api.$use.products.list('electronics'); // Fetches electronics
await api.$use.products.detail(42); // Fetches product 42
// Access plain values
api.$use.products.price; // 29.99Step 5: Use in Your Application
import { useQuery } from '@tanstack/react-query';
function ProductList({ category }: { category?: string }) {
const { data } = useQuery({
queryKey: api.products.list.$use(category),
queryFn: () => api.$use.products.list(category)
});
return <div>{/* render products */}</div>;
}Understanding Prefixes
Prefixes act as a namespace for your keys and appear at the beginning of every generated key:
const api = createBuilder(register, { prefix: ['app', 'cache'] });
api.products.list.$use(); // ['app', 'cache', 'products', 'list']
api.$get(); // ['app', 'cache'] - just the prefixUse prefixes to:
- Version your cache keys
- Separate concerns (e.g.,
['api']vs['ui']) - Namespace multiple builders in the same app
Key Takeaways
- Register: Your nested object containing keys and values
- Builder: The enhanced object created by
createBuilder() $use: Property on root for values, method on nodes for keys (follows function signature)$get(): Method for flexible key generation with arbitrary parameters- Type-safe: TypeScript enforces correct parameters throughout
Next Steps
- Learn about the builder object in detail
- See advanced usage patterns for real-world scenarios
- Understand registers and how to structure them