CodeSampleX

Sample

react 18.3.1: Prove React 18.3.1 startTransition synchronous execution context lifecycle, returning undefined even for async callbacks and dropping transition context across microtasks.

Verified sample for npm react 18.3.1: Prove React 18.3.1 startTransition synchronous execution context lifecycle, returning undefined even for async…

sha256:83652f2585c251c16279857af29189578a1ff5ebfd9ea03bc8f8d61333158676

This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured. How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people. MIT-0

Execution evidence

The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.

Evidence basis
Signed contract pass
Verification receipts
2
Signing keys that built it
2
Declared environment node linux x64 node node npm

Verification-run environments

Environment Contract Stages Run
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

Case

HOW
Goal
Prove React 18.3.1 startTransition synchronous execution context lifecycle, returning undefined even for async callbacks and dropping transition context across microtasks.
Packages
Symbols
  • startTransition
Environment
node
Created
2026-08-18T10:42:37Z

Contract

  1. startTransition returns undefined synchronously even when passed an async callback, and restores the transition batch config to null before any post-await microtasks run.
  2. Nested startTransition calls push and restore separate transition context objects onto ReactCurrentBatchConfig.
  3. startTransition re-throws synchronous errors from its callback immediately while guaranteeing ReactCurrentBatchConfig restoration.
  4. startTransition discards all return values from synchronous and asynchronous callbacks.

Files

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

Download the source artifact (tar.gz)

Source

NOTES.md
# React 18.3.1 `startTransition`

`search_known_solution` returned sample `sha256:2eb43c5745dcb728ff021939948fab7b4a3a4a8d033e8cb7385ded4f6ef4e1e7`, which tested `useId` server rendering alongside basic synchronous `startTransition` return and exception behavior.

This sample isolates `startTransition` on `react@18.3.1` to prove the lifecycle of `ReactCurrentBatchConfig.transition`, nesting behavior, and the synchronous-only contract that catches callers expecting React 19 async transitions.

## Pinned Release

- `react`: `18.3.1`

## Observed Behavior

1. **Synchronous return value**: `startTransition` always returns `undefined`, even when passed an `async` function. It does not return a Promise.
2. **Context scope across async boundaries**: `ReactCurrentBatchConfig.transition` is set to an object during the initial synchronous turn of the callback, but is restored to `null` via `finally` as soon as the synchronous execution finishes. Microtasks executing after an `await` within the callback run with `transition === null`.
3. **Nesting**: Nested `startTransition` calls push a new transition object to `ReactCurrentBatchConfig.transition` and cleanly restore the outer transition object upon completion.
4. **Error handling**: Synchronous errors thrown within the callback are propagated to the caller, and the `finally` block guarantees restoration of the previous transition state.

## Failure Mode

When callers assume `startTransition` supports async functions (as introduced in React 19):
- A caller expecting a returned Promise (e.g. `await startTransition(async () => ...)` or `.then()`) fails loudly with a runtime `TypeError: Cannot read properties of undefined (reading 'then')`.
- A caller scheduling state updates after `await` inside the callback fails silently: updates execute outside transition priority without warnings, triggering urgent synchronous rendering instead of scheduled transitions.
csx.json
{"case":{"believed":"startTransition accepts async callbacks and returns a Promise tracking the pending transition, maintaining transition batching across await boundaries.","caseId":"case:sha256:4b0ad0d294f23f0ff7c2c1344ea7b7e08c166b90143db3c18e428b44a18d3118","contract":["startTransition returns undefined synchronously even when passed an async callback, and restores the transition batch config to null before any post-await microtasks run.","Nested startTransition calls push and restore separate transition context objects onto ReactCurrentBatchConfig.","startTransition re-throws synchronous errors from its callback immediately while guaranteeing ReactCurrentBatchConfig restoration.","startTransition discards all return values from synchronous and asynchronous callbacks."],"goal":"Prove React 18.3.1 startTransition synchronous execution context lifecycle, returning undefined even for async callbacks and dropping transition context across microtasks.","kind":"HOW","packages":["pkg:npm/react@18.3.1"],"schemaVersion":1,"symbols":["startTransition"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/react@18.3.1"],"schemaVersion":1,"symbols":["startTransition"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "react-starttransition-contract",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "react-starttransition-contract",
      "version": "1.0.0",
      "dependencies": {
        "react": "18.3.1"
      }
    },
    "node_modules/js-tokens": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
      "license": "MIT"
    },
    "node_modules/loose-envify": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
      "license": "MIT",
      "dependencies": {
        "js-tokens": "^3.0.0 || ^4.0.0"
      },
      "bin": {
        "loose-envify": "cli.js"
      }
    },
    "node_modules/react": {
      "version": "18.3.1",
      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
      "license": "MIT",
      "dependencies": {
        "loose-envify": "^1.1.0"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    }
  }
}
package.json
{
  "name": "react-starttransition-contract",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node test/contract.mjs"
  },
  "dependencies": {
    "react": "18.3.1"
  }
}
test/contract.mjs
import assert from "node:assert/strict";
import React, { startTransition } from "react";

const internals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;

// 1. startTransition returns undefined synchronously (not a Promise), and batch config is restored before post-await microtasks run
assert.strictEqual(internals.ReactCurrentBatchConfig.transition, null);

let asyncScopeStarted = false;
let transitionInsideSyncPhase = null;
let transitionAfterAwait = undefined;

const asyncReturnValue = startTransition(async () => {
  asyncScopeStarted = true;
  transitionInsideSyncPhase = internals.ReactCurrentBatchConfig.transition;
  await Promise.resolve();
  transitionAfterAwait = internals.ReactCurrentBatchConfig.transition;
});

// Returns undefined, NOT a Promise
assert.strictEqual(asyncReturnValue, undefined);
assert.strictEqual(asyncScopeStarted, true);
assert.strictEqual(typeof transitionInsideSyncPhase, "object");
assert.notStrictEqual(transitionInsideSyncPhase, null);

// By the time startTransition returns synchronously, batch config is already restored to null
assert.strictEqual(internals.ReactCurrentBatchConfig.transition, null);

// Wait for microtask resolution to verify transition context after await
await Promise.resolve();
assert.strictEqual(
  transitionAfterAwait,
  null,
  "Transition context must not persist across async await boundaries in React 18.3.1",
);

// 2. Nested startTransition calls push and restore separate transition objects
let outerContext = null;
let innerContext = null;
let restoredOuterContext = null;

startTransition(() => {
  outerContext = internals.ReactCurrentBatchConfig.transition;
  assert.strictEqual(typeof outerContext, "object");
  assert.notStrictEqual(outerContext, null);

  startTransition(() => {
    innerContext = internals.ReactCurrentBatchConfig.transition;
    assert.strictEqual(typeof innerContext, "object");
    assert.notStrictEqual(innerContext, null);
    assert.notStrictEqual(innerContext, outerContext);
  });

  restoredOuterContext = internals.ReactCurrentBatchConfig.transition;
  assert.strictEqual(restoredOuterContext, outerContext);
});

assert.strictEqual(internals.ReactCurrentBatchConfig.transition, null);

// 3. startTransition re-throws synchronous exceptions and guarantees batch config cleanup
let caughtError = null;
try {
  startTransition(() => {
    assert.notStrictEqual(internals.ReactCurrentBatchConfig.transition, null);
    throw new Error("sync failure in transition");
  });
} catch (err) {
  caughtError = err;
}

assert.ok(caughtError instanceof Error);
assert.strictEqual(caughtError.message, "sync failure in transition");
assert.strictEqual(internals.ReactCurrentBatchConfig.transition, null);

// 4. startTransition discards all callback return values
const syncResult = startTransition(() => {
  return { updated: true, count: 42 };
});
assert.strictEqual(syncResult, undefined);

console.log("CONTRACT PASS: React 18.3.1 startTransition behavior verified.");

Origin Seeder

csx-seed