Fragno
Client frameworks

Vanilla JS

Learn how to integrate Fragno fragments into your JavaScript frontend

Edit on GitHub

Vanilla JavaScript support is included in the @fragno-dev/core package.

To start, create a client instance and call the Vanilla JavaScript useFragno function.

lib/example-fragment-client.ts
import { createExampleFragmentClient } from "@fragno-dev/example-fragment";
import { useFragno } from "@fragno-dev/core/vanilla";

const exampleFragmentClient = createExampleFragmentClient();
export const exampleFragment = useFragno(exampleFragmentClient);

Basic Usage

Use the subscribe method to listen for changes to the store. This method will call your callback immediately with the current value, then again for every future change.

main.ts
import { exampleFragment } from "@/lib/example-fragment-client";

exampleFragment.useData().subscribe(({ data, loading }) => {
  const dataDisplay = document.getElementById("data-display")!;
  dataDisplay.textContent = loading ? "loading..." : data;
});

Understanding Store Methods

Fragno stores provide three different ways to observe data changes: listen, subscribe, and the async iterator. Each serves a different purpose:

subscribe - Immediate + Future Updates

The subscribe method calls your callback immediately with the current value, then calls it again for every future change. This is the most common method for reactive UI updates.

const store = exampleFragment.useData();

// This will be called immediately with current data, then on every change
const unsubscribe = store.subscribe(({ data, loading, error }) => {
  console.log("Current state:", { data, loading, error });
  updateUI({ data, loading, error });
});

// Unsubscribe when done listening
unsubscribe();

listen - Future Updates Only

The listen method only calls your callback for future changes, not the current value. Use this when you only want to react to updates, not the initial state.

const store = exampleFragment.useData();

// This will only be called for future changes, not the current value
const unsubscribe = store.listen(({ data, loading, error }) => {
  console.log("State changed:", { data, loading, error });
  // Handle the change...
});

unsubscribe();

Async Iterator - Stream Processing

The async iterator allows you to process data changes as a stream using for await loops. This is useful for complex data processing or when you need to wait for specific conditions.

const store = exampleFragment.useData();

// Process each state change as it happens
for await (const { data, loading, error } of store) {
  if (loading) {
    console.log("Loading...");
  } else if (error) {
    console.error("Error:", error);
  } else {
    console.log("Data received:", data);
    // Process the data...
  }
}

Getting Current State

You can also get the current state synchronously without subscribing:

const store = exampleFragment.useData();
const currentState = store.get();
console.log("Current state:", currentState);

Complex Scenarios

Todo App Example

main.js
import { exampleFragment } from "@/lib/example-fragment-client";

// Access the stores
const todosStore = exampleFragment.useTodos();
const addTodoStore = exampleFragment.useAddTodo();

// Subscribe to todos data - this will update UI immediately and on changes
todosStore.subscribe(({ data: todos, loading, error }) => {
  if (loading) {
    document.getElementById("loading").style.display = "block";
  } else {
    document.getElementById("loading").style.display = "none";

    if (error) {
      document.getElementById("error").textContent = error.message;
    } else {
      renderTodos(todos);
    }
  }
});

// Listen only to changes (not initial state) for analytics
todosStore.listen(({ data: todos }) => {
  // Track when todos change for analytics
  analytics.track("todos_updated", { count: todos?.length || 0 });
});

// Trigger actions
document.getElementById("add-todo").addEventListener("click", async () => {
  await addTodoStore.mutate({ body: { text: "New todo" } });
});

function renderTodos(todos) {
  const container = document.getElementById("todos");
  container.innerHTML =
    todos?.map((todo) => `<div>${todo.text} - ${todo.done ? "Done" : "Pending"}</div>`).join("") ||
    "";
}