HYDROXIDE

Reactive State

Create and update reactive state in Hydroxide

Frameworks like React re-run your entire component when state changes, then diff the output to find what changed. Hydroxide skips all of that:

  1. No re-renders - Components run once per instance
  2. No diffing - Updates go directly to the DOM nodes that needs to be updated
  3. No dependency arrays - Hydroxide tracks dependencies automatically - no need to specify them manually

Reactives

A reactive is a container for a value that can notify the system when it changes. Create one with reactive():

import { reactive } from 'hydroxide'

const count = reactive(0)
const name = reactive('Alice')
const user = reactive({ name: 'Bob', age: 25 })

Reading: Call the reactive as a function to get its current value:

The reason why its a function is because the framework needs to know when the reactive value is being read. It being a function allows the framework to track when and how the value is being read to create a dependency graph.

console.log(count()) // 0
console.log(name()) // 'Alice'

Writing: Use set() to replace the value, or do() to transform it:

count.set(10) // replace with 10
count.do(v => v + 1) // increment by 1

When you read a reactive inside JSX, Hydroxide automatically tracks it. When the value changes, only that part of the DOM updates:

function Counter() {
  const count = reactive(0)

  function increment() {
    count.do(v => v + 1)
  }

  return (
    <button on-click={increment}>
      Count is {count()} {/* <- only this text node will be updated when `count` updates */}
    </button>
  )
}
import { reactive } from 'hydroxide';

function Example() {
  const count = reactive(0);

  function increment() {
    count.set(count() + 1);
  }

  // open the console to see the logs
  // when state is updated, component does not re-render
  console.log('No renders!')

  return (
    <button on-click={increment} class="primary-button">
      count is {count()}
    </button>
  );
}

export default Example;

Effects

Effects run side effects once on mount, and again whenever their tracked reactive values change:

import { reactive, effect } from 'hydroxide'

const count = reactive(0)

effect(() => {
  console.log('count is now', count())
})

The effect runs immediately when created, and Hydroxide tracks which reactives you read inside it. When any of them change, the effect re-runs.

import { reactive, effect } from 'hydroxide';

function Example() {
  const countA = reactive(0);
  const countB = reactive(0);

  // Only tracks countA - ignores countB changes
  // open the console to see the logs
  effect(() => {
    console.log('Counter A updated:', countA());
  });

  function incrementA() {
    countA.set(countA() + 1);
  }

  function incrementB() {
    countB.set(countB() + 1);
  }

  return (
    <div class="container">
      <button
        on-click={incrementA} class="primary-button">
        A is {countA()}
      </button>
      <button on-click={incrementB} class="primary-button">
        B is {countB()}
      </button>
    </div>
  );
}

export default Example;


What Gets Tracked

Tracking only happens in specific contexts:

ContextTracked?
JSX expressions {count()}✅ Yes
Inside effect()✅ Yes
Regular function body❌ No
function Counter() {
  const count = reactive(0)

  // Not tracked - runs once at setup
  console.log('Initial:', count())

  // Tracked - runs once on mount, then on every change
  effect(() => {
    console.log('Updated:', count())
  })

  // Tracked - updates the DOM
  return <p>Count: {count()}</p>
}

Shared State

Reactives can be created outside of components. Components can use them directly. When a reactive is updated - all the components that use it will be updated.

import { reactive } from 'hydroxide';

// State created outside components - shared globally
const count = reactive(0);

function DisplayA() {
  return (
    <div class="card">
      <span class="label">Display A</span>
      <span class="value">{count()}</span>
    </div>
  );
}

function DisplayB() {
  return (
    <div class="card">
      <span class="label">Display B</span>
      <span class="value">{count()}</span>
    </div>
  );
}

function Controls() {
  return (
    <div class="controls">
      <button on-click={() => count.do(v => v - 1)}></button>
      <button on-click={() => count.do(v => v + 1)}>+</button>
    </div>
  );
}

function App() {
  return (
    <div class="app">
      <div class="displays">
        <DisplayA />
        <DisplayB />
      </div>
      <Controls />
    </div>
  );
}

export default App;

On this page