Skip to main content

πŸ“š Examples πŸ’‘ 🐿️ πŸ¦†

Here are some practical examples showing how to use jods in different contexts.

🧩 Basic Usage​

This example demonstrates the core features of jods, including store creation, computed properties, change tracking, and JSON snapshots.

import { store, json, onUpdate, computed, diff } from "jods";

// Define the User interface
interface User {
firstName: string;
lastName: string;
email: string;
notifications: {
email: boolean;
push: boolean;
};
favorites: string[];
// Don't specify the type for fullName, let TypeScript infer it
}

// Create a user store
const user = store<User>({
firstName: "Burt",
lastName: "Macklin",
email: "Burt.Macklin@FBI.confidential",
notifications: {
email: true,
push: false,
},
favorites: [],
});

// Add a computed property
user.fullName = computed(() => `${user.firstName} ${user.lastName}`);

// Get a JSON snapshot
console.log("Initial state:", json(user));

// Create a reference to previous state
let lastState = json(user);

// Track update count to demonstrate granular updates
let updateCount = 0;

// Subscribe to changes - onUpdate fires ONCE for EACH property change
onUpdate(user, (newState, oldState) => {
updateCount++;
console.log(`User updated! (Update #${updateCount})`);
console.log("Changes:", diff(oldState, newState));
console.log("New state:", json(newState));

lastState = json(user);
console.log("Last state:", lastState);
});

// Make some changes - each one triggers a separate onUpdate callback
user.firstName = "Burt Macklin";
user.notifications.push = true;
user.favorites.push("undercover missions");

// Show the final state
console.log(`Total updates triggered: ${updateCount}`);
console.log("Final state with computed values:", json(user));

βš›οΈ React Integration​

This example shows how to use jods with React components using the useJods hook.

import React, { ChangeEvent } from "react";
import { store, computed } from "jods";
import { useJods } from "jods/react";

// Define the User interface
interface User {
firstName: string;
lastName: string;
occupation: string;
isUndercover: boolean;
skills: string[];
}

// Create a shared store
const userStore = store<User>({
firstName: "Burt",
lastName: "Macklin",
occupation: "FBI",
isUndercover: false,
skills: ["investigation", "disguise"],
});

// Add computed properties
userStore.fullName = computed(
() => `${userStore.firstName} ${userStore.lastName}`
);
userStore.displayTitle = computed(
() =>
`${userStore.fullName}, ${
userStore.isUndercover ? "Undercover Agent" : userStore.occupation
}`
);

// A component that displays user info
function UserProfile(): React.ReactElement {
// Use the hook to get the latest state
const user = useJods(userStore);

return (
<div className="user-profile">
<h2>{user.displayTitle}</h2>

<div className="user-skills">
<h3>Skills:</h3>
<ul>
{user.skills.map((skill, index) => (
<li key={index}>{skill}</li>
))}
</ul>
<button onClick={() => userStore.skills.push("surveillance")}>
Add Surveillance Skill
</button>
</div>

<div className="user-actions">
<button onClick={() => (userStore.isUndercover = !user.isUndercover)}>
{user.isUndercover ? "Blow Cover" : "Go Undercover"}
</button>
</div>
</div>
);
}

// A component that edits user info
function UserEditor(): React.ReactElement {
const user = useJods(userStore);

const handleFirstNameChange = (e: ChangeEvent<HTMLInputElement>): void => {
userStore.firstName = e.target.value;
};

const handleLastNameChange = (e: ChangeEvent<HTMLInputElement>): void => {
userStore.lastName = e.target.value;
};

return (
<div className="user-editor">
<h3>Edit Agent Profile</h3>
<div>
<label>
First Name:
<input
type="text"
value={user.firstName}
onChange={handleFirstNameChange}
/>
</label>
</div>
<div>
<label>
Last Name:
<input
type="text"
value={user.lastName}
onChange={handleLastNameChange}
/>
</label>
</div>
</div>
);
}

// Main app component
function App(): React.ReactElement {
return (
<div className="app">
<h1>JODS React Example</h1>
<UserProfile />
<UserEditor />
</div>
);
}

⚑️ Preact Integration​

jods includes native Preact support through a dedicated entry point. The API is identical to the React integration, making it easy to use in Preact applications.

import { h } from "preact";
import { useState } from "preact/hooks";
import { store, computed } from "jods";
import { useJods } from "jods/preact";

// Define a cart store
const cart = store({
items: [],
couponCode: "",
});

// Add computed properties
cart.itemCount = computed(() => cart.items.length);
cart.subtotal = computed(() =>
cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
cart.discount = computed(() =>
cart.couponCode === "SAVE20" ? cart.subtotal * 0.2 : 0
);
cart.total = computed(() => cart.subtotal - cart.discount);

// Product list component
function ProductList() {
const products = [
{ id: 1, name: "Widget", price: 9.99 },
{ id: 2, name: "Gadget", price: 14.99 },
{ id: 3, name: "Doohickey", price: 19.99 },
];

function addToCart(product) {
const existingItem = cart.items.find((item) => item.id === product.id);

if (existingItem) {
existingItem.quantity += 1;
} else {
cart.items.push({
id: product.id,
name: product.name,
price: product.price,
quantity: 1,
});
}
}

return (
<div class="products">
<h2>Products</h2>
{products.map((product) => (
<div key={product.id} class="product">
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
))}
</div>
);
}

// Shopping cart component
function ShoppingCart() {
const cartState = useJods(cart);
const [coupon, setCoupon] = useState("");

function applyCoupon() {
cart.couponCode = coupon;
}

function removeItem(itemId) {
cart.items = cart.items.filter((item) => item.id !== itemId);
}

return (
<div class="cart">
<h2>Shopping Cart ({cartState.itemCount} items)</h2>

{cartState.items.length === 0 ? (
<p>Your cart is empty</p>
) : (
<>
<ul>
{cartState.items.map((item) => (
<li key={item.id}>
{item.name} - ${item.price.toFixed(2)} x {item.quantity}
<button onClick={() => removeItem(item.id)}>Remove</button>
</li>
))}
</ul>

<div class="coupon">
<input
type="text"
value={coupon}
onInput={(e) => setCoupon(e.target.value)}
placeholder="Coupon code"
/>
<button onClick={applyCoupon}>Apply</button>
</div>

<div class="summary">
<p>Subtotal: ${cartState.subtotal.toFixed(2)}</p>
{cartState.discount > 0 && (
<p>Discount: -${cartState.discount.toFixed(2)}</p>
)}
<p class="total">Total: ${cartState.total.toFixed(2)}</p>
</div>
</>
)}
</div>
);
}

// Main app component
export function App() {
return (
<div class="app">
<h1>JODS Preact Shopping Cart Example</h1>
<div class="container">
<ProductList />
<ShoppingCart />
</div>
</div>
);
}

πŸ‘‚ Using onUpdate for Event Handling​

This example shows how to use the onUpdate function to track state changes.

import { store, onUpdate } from "jods";

// Create a store
const todoStore = store({
items: [],
filter: "all",
});

// Add a task to the store
function addTask(title) {
todoStore.items.push({
id: Date.now(),
title,
completed: false,
});
}

// Track changes and perform side effects
onUpdate(todoStore, (newState) => {
// Save to localStorage whenever state changes
localStorage.setItem("todos", JSON.stringify(newState.items));

// You could also sync with a server, log changes, etc.
console.log("Todo state updated:", newState);
});

// Add some tasks
addTask("Learn jods");
addTask("Build an app");

πŸ’Ώ Remix Integration​

jods provides a first-class integration with πŸ’Ώ Remix that simplifies state management across server and client. The integration combines loaders, actions, schema validation, and reactive client state.

Check out the πŸ’Ώ Remix Integration section for detailed examples and documentation, including:

  • πŸ”„ Defining stores with server-side data loading
  • βœ… Creating form handlers with automatic validation
  • ⚑ Implementing optimistic UI updates
  • πŸ›£οΈ Integrating with Remix's routing system

For more examples, check out the GitHub repository 🐿️ πŸ¦†.