React quickstart
Build a tiny world model: a US market event changes shared pressure; Japan and Europe interpret it against different local exposure.
Try it now#
This runs the exact example below, using authored fixtures and the real React runtime.
Install#
Use Node 24+ for tooling and React 18.2+ or 19.
npm install jev-hooks react react-domIn an existing React TypeScript app, replace App.tsx with this complete example. In Next.js, it can be a client page. No endpoint or API key is needed.
"use client";
import { useState } from "react";
import {
createJevClient,
JevProvider,
MockJudgmentAdapter,
SemanticScope,
useAmbient,
useNoul,
useScore,
} from "jev-hooks/react";
import type { ScoreAnswer, SemanticResult } from "jev-hooks/react";
// Authored fixtures, not live Jev. No API key required.
const mock = new MockJudgmentAdapter(({ state, questions }) => {
const input = state as Record<string, unknown>;
return Object.fromEntries(
Object.entries(questions).map(([id, question]) => {
if (question.type === "score") {
const score = Number(input.changePct) <= -3 ? 2 : 0;
return [id, { type: "score" as const, score }];
}
const pressure = input.pressure as { score: number };
return [
id,
{
type: "noul" as const,
noul: pressure.score === 2 ? Number(input.exposure) : 0.1,
},
];
}),
);
});
function Region({ name, exposure }: { name: string; exposure: number }) {
const pressure = useAmbient<SemanticResult<ScoreAnswer>>("pressure");
const attention = useNoul({
state: { pressure, exposure },
question: "Does this region need immediate attention given its exposure?",
});
return (
<p>
{name}:{" "}
{attention.error
? "Could not evaluate"
: attention.pending || attention.stale || !attention.data
? "Interpreting…"
: `${Math.round((attention.data?.noul ?? 0) * 100)}% attention`}
</p>
);
}
function World() {
const [changePct, setChangePct] = useState(0);
const pressure = useScore({
state: { changePct },
question: "How much international economic pressure exists?",
levels: ["Normal", "Elevated", "Severe"],
});
return (
<SemanticScope values={{ pressure }}>
<h1>One fact. Different consequences.</h1>
<p>SIMULATED · US market: {changePct}%</p>
<button onClick={() => setChangePct(changePct === 0 ? -4 : 0)}>
{changePct === 0 ? "Send US market drop" : "Reset"}
</button>
<Region name="Japan" exposure={0.9} />
<Region name="Europe" exposure={0.6} />
</SemanticScope>
);
}
export default function App() {
const [client] = useState(() => createJevClient({ adapter: mock }));
return (
<JevProvider client={client}>
<World />
</JevProvider>
);
}What happens#
- Clicking the button updates an ordinary React number.
useScoreinterprets that fact. Its input changed, so its previous answer becomes stale.SemanticScopemakes the pressure reference available to both region components.- Each region explicitly reads it with
useAmbient("pressure"). - Passing that reference into
useNoulcreates a dependency. The hook waits for fresh pressure, then evaluates with its own exposure.
The fixtures intentionally produce different regional answers. Only the answer source is simulated; dependency discovery, waiting, caching and React propagation use the real runtime.
Switch to live Jev#
After configuring an authenticated server endpoint, replace the client initializer:
const [client] = useState(() => createJevClient({ endpoint: "/api/jev" }));Keep the client stable across renders. Same-origin session cookies accompany requests. Provider credentials stay on the server. The private bearer-token starters in the hosting guides must be adapted to your application's session authorization before connecting a public browser; never embed their shared server token in client code.
Read versus compose#
Render attention.data?.noul, attention.pending, and attention.error. To compose, pass the original attention reference, or attention.select(answer => answer.noul). Passing .data alone loses dependency and readiness metadata.
Read composition next, or consult the full React API.