State Management ​

As your application grows, you may need to share state between multiple components or persist state across page reloads. Lune provides a simple and effective way to manage global state using the reactive function.

The Store Pattern ​

The simplest way to manage global state is to create a reactive object and share it between your components. This is often called the "Store Pattern".

1. Create a Store ​

Create a JavaScript file (e.g., store.js) to hold your global state.

js
import { reactive } from "lune-js";

export const store = reactive({
  count: 0,
  user: null,

  increment() {
    this.count++;
  },

  setUser(user) {
    this.user = user;
  }
});

2. Use the Store in Components ​

You can now import this store and use it in your components.

html
<script type="module">
  import { createApp } from "lune-js";
  import { store } from "./store.js";

  createApp({
    store
  }).mount();
</script>

<div lu-scope>
  <p>Count: {{ store.count }}</p>
  <button @click="store.increment()">Increment</button>
</div>

<div lu-scope>
  <p>Same Count: {{ store.count }}</p>
</div>

Because store is reactive, any change to store.count will automatically update all components that use it.

Global State with Lune.reactive ​

If you are using the CDN build without ES modules, you can still achieve this pattern.

html
<script src="https://cdn.jsdelivr.net/npm/lune-js"></script>

<script>
  const store = Lune.reactive({
    count: 0,
    increment() {
      this.count++;
    }
  });

  Lune.createApp({
    store
  }).mount();
</script>

<div lu-scope>
  {{ store.count }}
  <button @click="store.increment()">+</button>
</div>

Persisting State ​

To persist state across page reloads (e.g., for a dark mode preference), you can use localStorage combined with lu-effect or effect.

Using lu-effect ​

Template expressions cannot reach localStorage or document (see Security), so keep the persistence in a store method and let lu-effect call it. The method reads darkMode, which is what makes the effect re-run when it changes.

html
<script src="https://cdn.jsdelivr.net/npm/lune-js"></script>

<script>
  const store = Lune.reactive({
    darkMode: localStorage.getItem("darkMode") === "true",
    toggleTheme() {
      this.darkMode = !this.darkMode;
    },
    persist() {
      localStorage.setItem("darkMode", this.darkMode);
      document.body.classList.toggle("dark", this.darkMode);
    }
  });

  Lune.createApp({ store }).mount();
</script>

<div lu-scope lu-effect="store.persist()">
  <button @click="store.toggleTheme()">{{ store.darkMode ? 'Dark' : 'Light' }} Mode</button>
</div>

Using effect ​

html
<script type="module">
  import { createApp, reactive, effect } from "lune-js";

  const store = reactive({
    darkMode: localStorage.getItem("darkMode") === "true",
    toggleTheme() {
      this.darkMode = !this.darkMode;
    }
  });

  // React to darkMode changes and persist
  effect(() => {
    localStorage.setItem("darkMode", store.darkMode);
    document.body.classList.toggle("dark", store.darkMode);
  });

  createApp({ store }).mount();
</script>

<div lu-scope>
  <button @click="store.toggleTheme()">{{ store.darkMode ? 'Dark' : 'Light' }} Mode</button>
</div>

TIP

effect runs immediately; lu-effect runs once the surrounding template has been walked. Both then re-run whenever a reactive dependency changes, which makes them a good fit for side effects like saving to localStorage.