SuperNode
App

Hooks and Context

State, effects and shared values in App pages.

Call Hooks at the top level of function components, in a consistent order. Import them from supernode/app. Hooks only run inside app.show(); calling one elsewhere, including in a widget view, throws an error.

useState

useState(initial) returns [value, setValue]. initial may be a value or a function that runs once to produce the first value. setValue accepts the next value or an updater (current) => next. Equal values (Object.is) skip a re-render; the setter identity is stable.

import { app } from "supernode";
import { Column, Text, TextField, useState } from "supernode/app";

function Profile() {
  const [name, setName] = useState(() => "SuperNode");
  return (
    <Column spacing={12}>
      <Text fontSize={24}>Hello, {name}</Text>
      <TextField value={name} placeholder="Name" onValueChange={setName} />
    </Column>
  );
}

await app.show({ views: <Profile /> });

useReducer

useReducer(reducer, initialArg, init?) returns [state, dispatch] for state that updates through a reducer. When init is provided, the initial state is init(initialArg) and that function runs once. dispatch is stable and applies actions against the latest reducer.

import { app } from "supernode";
import { Column, Text, TextField, useReducer } from "supernode/app";

function reducer(state: { name: string }, name: string) {
  return { name };
}

function Form() {
  const [state, dispatch] = useReducer(reducer, "SuperNode", name => ({ name }));
  return (
    <Column spacing={12}>
      <Text fontSize={24}>Hello, {state.name}</Text>
      <TextField value={state.name} placeholder="Name" onValueChange={dispatch} />
    </Column>
  );
}

await app.show({ views: <Form /> });

useEffect

useEffect(setup, dependencies?) runs setup after the native page mounts or updates. setup may return a synchronous cleanup function. [] runs once per mount; omitting the list runs after every render. Cleanup runs before the next setup and when the page closes. Async setup is unsupported.

import { app } from "supernode";
import { Text, useEffect, useState } from "supernode/app";

function Clock() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setSeconds(value => value + 1), 1000);
    return () => clearInterval(timer);
  }, []);
  return <Text>{seconds} seconds</Text>;
}

await app.show({ views: <Clock /> });

useMemo

useMemo(calculate, dependencies?) keeps the result of calculate while the dependency values remain equal. Recalculate when a dependency changes. [] computes once per mount; omitting the list computes every render. Keep the dependency list the same length across renders.

import { app } from "supernode";
import { Column, Text, TextField, useMemo, useState } from "supernode/app";

function Preview() {
  const [title, setTitle] = useState("SuperNode");
  const label = useMemo(() => title.trim() || "Untitled", [title]);
  return (
    <Column spacing={12}>
      <Text fontSize={24}>{label}</Text>
      <TextField value={title} placeholder="Title" onValueChange={setTitle} />
    </Column>
  );
}

await app.show({ views: <Preview /> });

useCallback

useCallback(callback, dependencies?) keeps the same function instance while dependencies remain equal. Use it when a child or an effect depends on callback identity. [] retains the function for the component lifetime; omitting the list returns a new function every render.

import { app } from "supernode";
import { Column, Text, TextField, useCallback, useState } from "supernode/app";

function Profile() {
  const [name, setName] = useState("");
  const onValueChange = useCallback((value: string) => setName(value.trim()), []);
  return (
    <Column spacing={12}>
      <Text>Hello, {name || "guest"}</Text>
      <TextField value={name} placeholder="Name" onValueChange={onValueChange} />
    </Column>
  );
}

await app.show({ views: <Profile /> });

useContext

createContext(defaultValue) creates a context with Provider and Consumer. useContext(context) returns the nearest Provider value, or the default when none is present. Consumer children must be a function.

import { app } from "supernode";
import { createContext, Text, useContext } from "supernode/app";

const Greeting = createContext("Hello");

function Message() {
  const greeting = useContext(Greeting);
  return <Text>{greeting}</Text>;
}

await app.show({
  views: (
    <Greeting.Provider value="Welcome">
      <Message />
    </Greeting.Provider>
  ),
});

Rules

State setters schedule batched rendering. Keep rendering pure, and release external resources in effect cleanup. Hook calls outside a component, inconsistent Hook order, and updates after unmount produce errors.

Widgets render once and do not support Hooks or Context; see Widget.

On this page