Quick Start

From zero to productive in 2 minutes

Install it

npm install zenbox

Your first store

Let's build a simple counter. Here's the entire thing:

import { createStore, useStore } from "zenbox";

// Create the store - types are inferred automatically
const counter = createStore({
  count: 0,
  increment: () =>
    counter.setState((s) => {
      s.count++;
    }),
  decrement: () =>
    counter.setState((s) => {
      s.count--;
    }),
  reset: () => counter.setState({ count: 0 }),
});

// Use it in a component
function Counter() {
  const { count, increment, decrement, reset } = useStore(counter);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

That's it. No interfaces, no providers, no setup. It just works.

Add some Vue magic

Now let's add computed values and watchers:

import { useComputed, useWatch } from "zenbox";

function EnhancedCounter() {
  // Computed values - update automatically when dependencies change
  const doubled = useComputed(() => counter.value.count * 2);
  const isEven = useComputed(() => counter.value.count % 2 === 0);

  // Watch for changes
  useWatch(
    () => counter.value.count,
    (newCount, oldCount) => {
      if (newCount > 10) {
        console.log("Getting high!");
      }
    }
  );

  return (
    <div>
      <p>Doubled: {doubled}</p>
      <p>Is even: {isEven ? "Yes" : "No"}</p>
      <button onClick={counter.value.increment}>Increment</button>
    </div>
  );
}

Multiple stores? No problem

const user = createStore({
  name: "Alice",
  email: "alice@example.com",
});

const posts = createStore({
  items: [],
  addPost: (title) =>
    posts.setState((s) => {
      s.items.push({ id: Date.now(), title, author: user.value.name });
    }),
});

function Summary() {
  // Compute across stores - ZenBox tracks dependencies automatically
  const userSummary = useComputed(
    () => `${user.value.name} has written ${posts.value.items.length} posts`
  );

  return <p>Summary: {userSummary}</p>;
}

Optimize re-renders with selectors

When your component only depends on specific fields from the store, use selectors to prevent unnecessary re-renders.

// ❌ Re-renders on any store change, even if age changes
const { name, email, age } = useStore(userStore);

// ✅ Only re-renders when name or email changes
const { name, email } = useStore(userStore, { pick: ["name", "email"] });

For convenience, use usePick as a shortcut for selecting specific fields:

// Same as above, but more concise
const { name, email } = usePick(userStore, "name", "email");

That's really it

You now know everything you need to be productive with ZenBox:

  • createStore() for your state
  • useStore() to subscribe to state changes
  • usePick() to optimize re-renders
  • useComputed() for derived values
  • useWatch() to react to changes

The rest is just building your app. Check out the other guides for advanced patterns.