useFieldContext crashes when react compiler is enabled in expo
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
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,
},
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.
You're right!
But I found a solution without turning off unstable_enablePackageExports.
const reactForm = require("@tanstack/react-form");
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.
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) {
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.
You're right!
But I found a solution without turning off unstable_enablePackageExports.
const reactForm = require("@tanstack/react-form");
Can you elaborate on this?
I managed to patch the
@tanstack/storeto workaround theinstanceof, 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.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
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.
I added a comment in another discussion with expo's esm incompatible libraries
https://github.com/expo/expo/discussions/36551#discussioncomment-13331447
up on this, this still happens on 1.15.1
react-native 0.79.5 expo 53.0.20