Skip to content

react-reconciler 0.33 production build: updateContainerSync throws 'value is not a function' on first render (minimal 30-line repro, no ink) — blocks all React renderers #5015

@proggeramlug

Description

@proggeramlug

Summary

Rendering anything through react-reconciler (0.33.0) + react (19.x) throws TypeError: value is not a function inside updateContainerSync. The reconciler builds and a container is created fine; the failure is in the synchronous render/commit path. Confirmed (via native backtrace) to be react-reconciler's production build — so it's a genuine codegen gap, not a dev-only path.

This is the wall immediately after #5009/#5014 (which correctly switched react-reconciler to its production build). It's the last thing between ink (#348) and yoga layout, and it blocks every custom React renderer.

Minimal repro (no ink, no yoga — ~30 lines)

import React from 'react';
import createReconciler from 'react-reconciler';
const noop = () => {};
const rec = createReconciler({
  supportsMutation: true, isPrimaryRenderer: true, supportsPersistence: false, supportsHydration: false, supportsMicrotasks: true,
  getRootHostContext: () => ({}), getChildHostContext: () => ({}), getPublicInstance: (i)=>i,
  prepareForCommit: () => null, resetAfterCommit: noop, clearContainer: noop,
  createInstance: () => ({ children: [] }), createTextInstance: () => ({ text: '' }),
  appendInitialChild: noop, finalizeInitialChildren: () => false, shouldSetTextContent: () => false,
  appendChild: noop, appendChildToContainer: noop, insertBefore: noop, removeChild: noop, removeChildFromContainer: noop,
  commitUpdate: noop, commitTextUpdate: noop, detachDeletedInstance: noop, getCurrentEventPriority: () => 0,
  maySuspendCommit: () => false, scheduleMicrotask: queueMicrotask, scheduleTimeout: setTimeout, cancelTimeout: clearTimeout, noTimeout: -1,
  preparePortalMount: noop, prepareScopeUpdate: noop, getInstanceFromScope: () => null, getInstanceFromNode: () => null,
  beforeActiveInstanceBlur: noop, afterActiveInstanceBlur: noop,
});
const container = rec.createContainer({ children: [] }, 0, null, false, null, 'id', ()=>{}, ()=>{}, ()=>{}, null);
function App() { return React.createElement('host', null, 'hi'); }
rec.updateContainerSync(React.createElement(App), container, null, noop);   // → TypeError: value is not a function
rec.flushSyncWork();

(compilePackages: ["react","react-reconciler","scheduler"] + allow.compilePackages, PERRY_TREE_SHAKE=1, perry.define NODE_ENV=production.)

Output:

[A] reconciler built
[B] container created
[Cx] render threw: value is not a function

Native backtrace (--debug-symbols, breakpoint on throw_not_callable)

#0  perry_runtime::closure::dispatch::throw_not_callable
#1  js_closure_call1
#2  perry_closure_node_modules_react_reconciler_cjs_react_reconciler_PRODUCTION_js__96
#3  …react_reconciler_PRODUCTION_js__89
#4  …react_reconciler_PRODUCTION_js__403
#5  …react_reconciler_PRODUCTION_js__458
#6  …react_reconciler_PRODUCTION_js__548
#7  js_native_call_value
#8  js_native_call_method
#9  Ink.render / (in the minimal repro: updateContainerSync)

So react-reconciler.production.js closure __96, reached via __548 → __458 → __403 → __89, calls a value through js_closure_call1 that Perry finds non-callable. (Registers are unavailable in the optimized frames, so the exact callee value couldn't be read from lldb — the closure indices __96/__89/__403/__458/__548 should map to source via --trace llvm on react_reconciler_cjs_react_reconciler_production_js.ll.)

Notes / triage

Impact

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions