form icon indicating copy to clipboard operation
form copied to clipboard

useFieldContext crashes when react compiler is enabled in expo

Open nathan-charles opened this issue 7 months ago • 10 comments

Describe the bug

App crashes when react compiler is enabled using expo 53

Warning: TypeError: useFieldContext is not a function (it is undefined)

Your minimal, reproducible example

None

Steps to reproduce

Implement useFieldContext with react compiler enabled in expo 53

Expected behavior

Should not cause error

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

  • Expo 53 with react compiler enabled
  • iOS

TanStack Form adapter

react-form

TanStack Form version

1.11.1

TypeScript version

No response

Additional context

No response

nathan-charles avatar May 12 '25 01:05 nathan-charles

I'm also trying to use TanStack Form in React Native, but I keep getting an error when react-compiler is enabled

import { TextInput, View, Text } from "react-native";
import { useForm } from "@tanstack/react-form";
import React from "react";

export default function Test() {
  const form = useForm({
    defaultValues: {
      email: "",
      password: "",
    },
    validators: {},
    onSubmit: (data) => {
      console.log(data);
    },
  });

  return (
    <View>
      <form.Field name="email">
        {(field) => (
          <React.Fragment>
            <Text>Email:</Text>
            <TextInput
              value={field.state.value}
              onChangeText={field.handleChange}
            />
          </React.Fragment>
        )}
      </form.Field>
    </View>
  );
}
"react-native": "0.79.2",
"expo": "^53.0.9",
"@tanstack/react-form": "^1.11.2",
experiments: {
	typedRoutes: true,
	tsconfigPaths: true,
	reactCanary: true,
	reactCompiler: true,
},

Image

abanobboles avatar May 20 '25 19:05 abanobboles

I'm not sure if this is cause by react compiler. In my case, it only happens when unstable_enablePackageExports is enabled, which by default on Expo 53. Setting it to false fixed the issue, even with react compiler enabled.

chungweileong94 avatar May 21 '25 15:05 chungweileong94

You're right!

But I found a solution without turning off unstable_enablePackageExports.

const reactForm = require("@tanstack/react-form");

abanobboles avatar May 21 '25 20:05 abanobboles

I managed to patch the @tanstack/store to workaround the instanceof, but I'm not entirely sure why, most likely an upstream bug from Hermes or react compiler.

Here is the patch file, instead of using instanceof, I just use a "brand" prop to check the instance type.

@tanstack__store.patch

diff --git a/dist/esm/derived.js b/dist/esm/derived.js
index 47b0562622358cc211a31d7e640863b6e09f0555..9848278b05968e3342ad31f07df68b36e9515855 100644
--- a/dist/esm/derived.js
+++ b/dist/esm/derived.js
@@ -2,6 +2,7 @@ import { Store } from "./store.js";
 import { __storeToDerived, __derivedToStore } from "./scheduler.js";
 class Derived {
   constructor(options) {
+    this.__brand = "Derived"
     this.listeners = /* @__PURE__ */ new Set();
     this._subscriptions = [];
     this.lastSeenDepValues = [];
diff --git a/dist/esm/scheduler.js b/dist/esm/scheduler.js
index 448dafd6115aef4962c73c19cf99251520e8afcf..4e7f728c7aaa6feb32149b5e8c72b438a571fb97 100644
--- a/dist/esm/scheduler.js
+++ b/dist/esm/scheduler.js
@@ -1,4 +1,3 @@
-import { Derived } from "./derived.js";
 const __storeToDerived = /* @__PURE__ */ new WeakMap();
 const __derivedToStore = /* @__PURE__ */ new WeakMap();
 const __depsThatHaveWrittenThisTick = {
@@ -10,8 +9,8 @@ const __pendingUpdates = /* @__PURE__ */ new Set();
 const __initialBatchValues = /* @__PURE__ */ new Map();
 function __flush_internals(relatedVals) {
   const sorted = Array.from(relatedVals).sort((a, b) => {
-    if (a instanceof Derived && a.options.deps.includes(b)) return 1;
-    if (b instanceof Derived && b.options.deps.includes(a)) return -1;
+    if (a.__brand === "Derived" && a.options.deps.includes(b)) return 1;
+    if (b.__brand === "Derived" && b.options.deps.includes(a)) return -1;
     return 0;
   });
   for (const derived of sorted) {

chungweileong94 avatar May 24 '25 09:05 chungweileong94

I'm reasonably sure this is happening due to instanceof expecting the exact class instance as Store and Form

I'm not entirely sure how to solve this; this feels like a bug in Expo...

I'll dive deeper in soon enough, but help in debugging more officially (without patching Form or Store's JS/TS code) would be great.

crutchcorn avatar May 29 '25 04:05 crutchcorn

You're right!

But I found a solution without turning off unstable_enablePackageExports.

const reactForm = require("@tanstack/react-form");

Can you elaborate on this?

jrhager84 avatar May 29 '25 05:05 jrhager84

I managed to patch the @tanstack/store to workaround the instanceof, but I'm not entirely sure why, most likely an upstream bug from Hermes or react compiler.

Here is the patch file, instead of using instanceof, I just use a "brand" prop to check the instance type.

@tanstack__store.patch

diff --git a/dist/esm/derived.js b/dist/esm/derived.js index 47b0562622358cc211a31d7e640863b6e09f0555..9848278b05968e3342ad31f07df68b36e9515855 100644 --- a/dist/esm/derived.js +++ b/dist/esm/derived.js @@ -2,6 +2,7 @@ import { Store } from "./store.js"; import { __storeToDerived, __derivedToStore } from "./scheduler.js"; class Derived { constructor(options) {

  • this.__brand = "Derived" this.listeners = /* @PURE / new Set(); this._subscriptions = []; this.lastSeenDepValues = []; diff --git a/dist/esm/scheduler.js b/dist/esm/scheduler.js index 448dafd6115aef4962c73c19cf99251520e8afcf..4e7f728c7aaa6feb32149b5e8c72b438a571fb97 100644 --- a/dist/esm/scheduler.js +++ b/dist/esm/scheduler.js @@ -1,4 +1,3 @@ -import { Derived } from "./derived.js"; const __storeToDerived = / @PURE / new WeakMap(); const __derivedToStore = / @PURE / new WeakMap(); const __depsThatHaveWrittenThisTick = { @@ -10,8 +9,8 @@ const __pendingUpdates = / @PURE / new Set(); const __initialBatchValues = / @PURE */ new Map(); function __flush_internals(relatedVals) { const sorted = Array.from(relatedVals).sort((a, b) => {
  • if (a instanceof Derived && a.options.deps.includes(b)) return 1;
  • if (b instanceof Derived && b.options.deps.includes(a)) return -1;
  • if (a.__brand === "Derived" && a.options.deps.includes(b)) return 1;
  • if (b.__brand === "Derived" && b.options.deps.includes(a)) return -1; return 0; }); for (const derived of sorted) {

Thanks! The patch worked for me

Code-Victor avatar May 29 '25 11:05 Code-Victor

You're right!

But I found a solution without turning off unstable_enablePackageExports.

const reactForm = require("@tanstack/react-form");

Can you elaborate on this?

Import the package via cjs can also workaround the issue. The issue is basically caused by enabled both React Compiler and ESM on RN.

chungweileong94 avatar May 29 '25 11:05 chungweileong94

I added a comment in another discussion with expo's esm incompatible libraries

https://github.com/expo/expo/discussions/36551#discussioncomment-13331447

fendyk avatar May 31 '25 21:05 fendyk

up on this, this still happens on 1.15.1

react-native 0.79.5 expo 53.0.20

patrikduksin avatar Aug 01 '25 03:08 patrikduksin