j/ Jev Hooks DOCS
JEV HOOKS / Start

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.

sh
npm install jev-hooks react react-dom

In 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.

tsx
"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>
  );
}

Download this example

What happens#

  1. Clicking the button updates an ordinary React number.
  2. useScore interprets that fact. Its input changed, so its previous answer becomes stale.
  3. SemanticScope makes the pressure reference available to both region components.
  4. Each region explicitly reads it with useAmbient("pressure").
  5. Passing that reference into useNoul creates 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:

tsx
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.