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.