Basic Usage Examples
Examples of basic Craft usage patterns.
Simple Object Updates
typescript
import { craft } from "@sylphx/craft";
const state = {
count: 0,
name: "Alice",
active: false
};
const next = craft(state, draft => {
draft.count = 10;
draft.name = "Bob";
draft.active = true;
});
console.log(next);
// { count: 10, name: "Bob", active: true }
console.log(state);
// { count: 0, name: "Alice", active: false } - unchangedIncrementing Values
typescript
const state = { count: 0, score: 100 };
const next = craft(state, draft => {
draft.count++;
draft.score += 10;
});
console.log(next); // { count: 1, score: 110 }Nested Objects
typescript
const state = {
user: {
profile: {
name: "Alice",
age: 25,
address: {
city: "New York",
country: "USA"
}
}
}
};
const next = craft(state, draft => {
draft.user.profile.age = 26;
draft.user.profile.address.city = "Boston";
});
// Only modified parts are copied
console.log(state.user.profile.name === next.user.profile.name); // true
console.log(state.user.profile.age === next.user.profile.age); // falseArrays - Adding Elements
typescript
const state = { items: [1, 2, 3] };
const next = craft(state, draft => {
draft.items.push(4);
draft.items.push(5);
});
console.log(next.items); // [1, 2, 3, 4, 5]Arrays - Updating Elements
typescript
const state = {
todos: [
{ id: 1, text: "Learn Craft", done: false },
{ id: 2, text: "Build app", done: false }
]
};
const next = craft(state, draft => {
draft.todos[0].done = true;
draft.todos[1].text = "Build amazing app";
});
console.log(next.todos[0].done); // true
console.log(next.todos[1].text); // "Build amazing app"Arrays - Removing Elements
typescript
import { craft } from "@sylphx/craft";
const state = { items: [1, 2, 3, 4, 5] };
// Remove by index
const next1 = craft(state, draft => {
draft.items.splice(2, 1); // Remove 3rd element
});
console.log(next1.items); // [1, 2, 4, 5]
// Remove by value
const next2 = craft(state, draft => {
const index = draft.items.indexOf(3);
if (index !== -1) {
draft.items.splice(index, 1);
}
});
console.log(next2.items); // [1, 2, 4, 5]Using nothing
typescript
import { craft, nothing } from "@sylphx/craft";
// Delete object property
const state = { name: "Alice", age: 25, temp: "delete me" };
const next = craft(state, draft => {
draft.temp = nothing;
});
console.log(next); // { name: "Alice", age: 25 }
// Remove array element
const state2 = { items: ["a", "b", "c", "d"] };
const next2 = craft(state2, draft => {
draft.items[1] = nothing; // Remove "b"
});
console.log(next2.items); // ["a", "c", "d"]Returning New Values
You can return a completely new value to replace the entire state:
typescript
const state = { count: 0, name: "Alice" };
const next = craft(state, draft => {
// Return new value - mutations are ignored
return { count: 100, name: "Bob" };
});
console.log(next); // { count: 100, name: "Bob" }Conditional returns:
typescript
const next = craft(state, draft => {
if (shouldReset) {
return { count: 0, name: "Default" };
}
draft.count++;
});Multiple Updates
typescript
const state = {
user: { name: "Alice", age: 25 },
settings: { theme: "light", notifications: true },
stats: { views: 0, likes: 0 }
};
const next = craft(state, draft => {
// Update user
draft.user.age = 26;
// Update settings
draft.settings.theme = "dark";
draft.settings.notifications = false;
// Update stats
draft.stats.views++;
draft.stats.likes += 5;
});Conditional Updates
typescript
const next = craft(state, draft => {
if (draft.count < 10) {
draft.count++;
}
if (draft.user.age >= 18) {
draft.user.verified = true;
}
if (draft.items.length === 0) {
draft.items.push("default");
}
});Type-Safe Updates
typescript
interface User {
name: string;
age: number;
email: string;
}
interface State {
user: User;
count: number;
}
const state: State = {
user: { name: "Alice", age: 25, email: "alice@example.com" },
count: 0
};
const next = craft(state, draft => {
draft.user.name = "Bob"; // ✅ OK
draft.count = 10; // ✅ OK
// draft.user.age = "invalid"; // ❌ Type error
// draft.nonexistent = true; // ❌ Type error
});No-op Detection
If no changes are made, the original state is returned:
typescript
const state = { count: 5 };
const next = craft(state, draft => {
// No changes
});
console.log(state === next); // true - same referenceThis is important for preventing unnecessary re-renders in React:
typescript
setState(current =>
craft(current, draft => {
// Only update if needed
if (condition) {
draft.value = newValue;
}
})
); // Won't cause re-render if condition is falseStructural Sharing
Unchanged parts of the state tree maintain their references:
typescript
const state = {
users: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
],
settings: { theme: "light" }
};
const next = craft(state, draft => {
draft.users[0].name = "Alice Smith";
});
// Only changed parts are new references
console.log(state.users === next.users); // false - array changed
console.log(state.users[0] === next.users[0]); // false - first user changed
console.log(state.users[1] === next.users[1]); // true - second user unchanged
console.log(state.settings === next.settings); // true - settings unchangedPractical Example: Form State
typescript
interface FormState {
values: {
name: string;
email: string;
age: number;
};
errors: Record<string, string>;
touched: Record<string, boolean>;
isSubmitting: boolean;
}
const initialState: FormState = {
values: { name: "", email: "", age: 0 },
errors: {},
touched: {},
isSubmitting: false
};
// Update field
const updateField = (field: string, value: any) =>
craft(state, draft => {
draft.values[field] = value;
draft.touched[field] = true;
// Clear error when field is edited
delete draft.errors[field];
});
// Set errors
const setErrors = (errors: Record<string, string>) =>
craft(state, draft => {
draft.errors = errors;
});
// Submit
const setSubmitting = (isSubmitting: boolean) =>
craft(state, draft => {
draft.isSubmitting = isSubmitting;
});
// Reset
const reset = () =>
craft(state, () => initialState);