Hooks

Composable, function-based primitives for state, events, data fetching, i18n, and more. The hooks module provides a familiar pattern inspired by React hooks but designed for vanilla JavaScript. Access everything through Aura.hooks.

Quick Start

The hooks module is available on every Aura instance. No additional imports are needed.

js
// Reactive state
const [count, setCount] = Aura.hooks.useState('count', 0);
setCount(count() + 1);

// Event subscription with auto-cleanup
const unsub = Aura.hooks.useEvent('user:login', (user) => {
  console.log('Welcome', user.name);
});

// Fetch data with loading state
const { data, loading, error, refetch } = Aura.hooks.useFetch('/api/users');

// Persist to localStorage
const [theme, setTheme] = Aura.hooks.useStorage('theme', 'dark');

register()

Register a custom hook by name so it can later be invoked with use(). This is the foundation for creating reusable, shareable hook logic.

register(name: string, fn: (...args: unknown[]) => unknown): void
ParamTypeDescription
namestringUnique name for the hook.
fn(...args) => unknownThe hook function to register.
js
Aura.hooks.register('useToggle', (key, initial = false) => {
  const [val, setVal] = Aura.hooks.useState(key, initial);
  const toggle = () => setVal(!val());
  return [val, toggle];
});

use()

Invoke a previously registered custom hook. Throws if the hook has not been registered.

use<T>(name: string, ...args: unknown[]): T
ParamTypeDescription
namestringName of the registered hook.
...argsunknown[]Arguments forwarded to the hook function.

Returns: The return value of the hook function, cast to T.

js
const [isOpen, toggleOpen] = Aura.hooks.use('useToggle', 'modal', false);

console.log(isOpen());  // false
toggleOpen();
console.log(isOpen());  // true

useState()

Creates a reactive state binding. Returns a [getter, setter] tuple backed by the Aura state store. If the key does not exist yet, it is initialized with the provided value.

useState<T>(key: string, initial?: T): [() => T, (value: T) => void]
ParamTypeDescription
keystringState key name.
initialTInitial value (only set if key is undefined).

Returns: [getter(), setter(value)] — the getter is a function you call to read the current value.

js
const [count, setCount] = Aura.hooks.useState('count', 0);

console.log(count());   // 0
setCount(5);
console.log(count());   // 5

// Multiple components can share the same key
const [count2] = Aura.hooks.useState('count');
console.log(count2());  // 5 (same underlying state)

useEvent()

Subscribe to an Aura event. Returns an unsubscribe function. Optionally accepts a priority (lower numbers fire first).

useEvent(event: string, handler: (...args: unknown[]) => void, priority?: number): () => void
ParamTypeDescription
eventstringEvent name to listen for.
handler(...args) => voidCallback invoked when the event fires.
prioritynumber?Optional priority. Lower numbers fire first.

Returns: () => void — call this to unsubscribe.

js
const unsub = Aura.hooks.useEvent('cart:update', (items) => {
  renderCart(items);
});

// With priority (fires before default handlers)
Aura.hooks.useEvent('cart:update', logAnalytics, 1);

// Clean up when done
unsub();

useComputed()

Derives a computed value from the state store. The function receives the full state object and should return the derived value. Returns a getter function.

useComputed<T>(key: string, fn: (state: Record<string, unknown>) => T): () => T
ParamTypeDescription
keystringKey under which the computed value is stored.
fn(state) => TDerivation function that receives the full state object.

Returns: () => T — getter that returns the latest computed value.

js
const total = Aura.hooks.useComputed('cartTotal', (state) => {
  return state.items.reduce((sum, i) => sum + i.price, 0);
});

console.log(total());  // e.g. 59.97

useFetch()

Declarative data fetching with reactive data, loading, and error getters. By default the request fires immediately; pass auto: false to defer it.

useFetch<T>(url: string, opts?: { method?: string; body?: unknown; auto?: boolean }): { data: () => T | null; loading: () => boolean; error: () => string | null; refetch: () => Promise<void> }
ParamTypeDescription
urlstringThe URL to fetch.
opts.methodstring?HTTP method. Defaults to "GET".
opts.bodyunknown?Request body (used for non-GET methods).
opts.autoboolean?Auto-fetch on creation. Defaults to true.

Returns: An object with reactive getters and a refetch method.

js
// Auto-fetch (default)
const { data, loading, error, refetch } = Aura.hooks.useFetch('/api/users');

// Check state reactively
if (loading()) showSpinner();
if (error())   showError(error());
if (data())    renderUsers(data());

// Deferred fetch
const post = Aura.hooks.useFetch('/api/users', {
  method: 'POST',
  body: { name: 'Ada' },
  auto: false
});
// Fire manually later
await post.refetch();

useI18n()

Returns a translate function that delegates to the Aura i18n module. Supports parameterized strings.

useI18n(): (key: string, params?: Record<string, string | number>) => string

Takes no arguments.

Returns: (key, params?) => string — a translation function.

js
const t = Aura.hooks.useI18n();

console.log(t('hero.title'));
// "Welcome to Aura"

console.log(t('greeting', { name: 'Ada' }));
// "Hello, Ada!"

useStorage()

Like useState(), but backed by localStorage. The value persists across page reloads. Returns a [getter, setter] tuple.

useStorage<T>(key: string, initial?: T): [() => T, (value: T) => void]
ParamTypeDescription
keystringlocalStorage key.
initialT?Default value if the key does not exist in storage.

Returns: [getter(), setter(value)] — reads/writes localStorage through the Aura storage module.

js
const [theme, setTheme] = Aura.hooks.useStorage('theme', 'dark');

console.log(theme());   // 'dark' (or last saved value)
setTheme('light');
// Survives page reload

// Great for user preferences
const [lang, setLang] = Aura.hooks.useStorage('locale', 'en');

useWatch()

Subscribe to changes on a specific state key. The handler fires whenever the value changes. Returns an unsubscribe function.

useWatch(key: string, handler: (value: unknown) => void): () => void
ParamTypeDescription
keystringState key to watch.
handler(value) => voidCallback receiving the new value.

Returns: () => void — call to stop watching.

js
const unsub = Aura.hooks.useWatch('user', (user) => {
  document.getElementById('name').textContent = user.name;
});

// Later, clean up
unsub();

useSetup()

Run a setup function that may call useEvent() and useWatch() internally. All subscriptions created inside the setup are automatically tracked. The returned teardown function cleans them all up at once.

useSetup(setupFn: () => void): () => void
ParamTypeDescription
setupFn() => voidFunction that sets up hooks. useEvent and useWatch calls inside are auto-tracked.

Returns: () => void — call to teardown all collected subscriptions.

js
const teardown = Aura.hooks.useSetup(() => {
  // All subscriptions are auto-tracked
  Aura.hooks.useEvent('resize', handleResize);
  Aura.hooks.useEvent('scroll', handleScroll);
  Aura.hooks.useWatch('user', updateUI);
  Aura.hooks.useWatch('theme', applyTheme);
});

// One call cleans up all 4 subscriptions
teardown();

Custom Hooks

You can build reusable hook abstractions by combining register() and use(). This pattern lets you share stateful logic across your application without coupling it to a component framework.

Creating a custom hook

js
// Register a reusable "useAuth" hook
Aura.hooks.register('useAuth', () => {
  const [user, setUser] = Aura.hooks.useState('auth.user', null);
  const [token, setToken] = Aura.hooks.useStorage('auth.token');

  const login = async (credentials) => {
    const { data } = Aura.hooks.useFetch('/api/login', {
      method: 'POST',
      body: credentials,
      auto: false
    });
    setUser(data());
    setToken(data().token);
  };

  const logout = () => {
    setUser(null);
    setToken(null);
  };

  return { user, token, login, logout };
});

Using a custom hook

js
const auth = Aura.hooks.use('useAuth');

if (auth.user()) {
  console.log('Logged in as', auth.user().name);
} else {
  await auth.login({ email: 'ada@example.com', password: '...' });
}

Another example: useDebounce

js
Aura.hooks.register('useDebounce', (key, delay = 300) => {
  const [value, setValue] = Aura.hooks.useState(key);
  const [debounced, setDebounced] = Aura.hooks.useState(key + '.debounced');
  let timer;

  Aura.hooks.useWatch(key, (val) => {
    clearTimeout(timer);
    timer = setTimeout(() => setDebounced(val), delay);
  });

  return { value, setValue, debounced };
});

const search = Aura.hooks.use('useDebounce', 'searchQuery', 500);

Comparison with React Hooks

If you are coming from React, here is how Aura hooks map to their React equivalents. The main difference is that Aura getters are functions you call, not raw values.

ReactAura.jsNotes
useState(0) hooks.useState('key', 0) Aura requires a string key. Getter is a function: count().
useEffect(() => ...) hooks.useSetup(() => ...) Collects useEvent/useWatch cleanups automatically.
useMemo(() => ...) hooks.useComputed('key', fn) Derives from global state, not local deps.
useContext(...) hooks.useState('key') All Aura state is globally shared — no provider needed.
useReducer(...) hooks.register('useReducer', ...) Build your own with register() + useState().
useRef() hooks.useState('ref.key') Use state with a unique key for mutable refs.
Custom Hook hooks.register() + hooks.use() Named registry instead of function imports.
js
// React
const [count, setCount] = useState(0);
return <p>{count}</p>;

// Aura.js
const [count, setCount] = Aura.hooks.useState('count', 0);
el.textContent = count();  // Note: getter is a function call