TypeError: ownKeys target is non-extensible but key is missing from trap result error when using numeric string keys on Hermes
Bug Description
When using Valtio with Hermes in React Native, an error is thrown if the proxy object contains numeric string keys. For example, using a key like '12345' results in the following error:
TypeError: ownKeys target is non-extensible but key is missing from trap result, js engine: hermes
I've confirmed that using non-numeric string keys (e.g. text) avoids the error.
- [x] I have run
gradle cleanand confirmed this bug does not occur with JSC - [ ] The issue is reproducible with the latest version of React Native.
Hermes git revision (if applicable):
React Native version: 0.76.5 (new arch is false)
OS: iOS, Android
Platform (most likely one of arm64-v8a, armeabi-v7a, x86, x86_64): arm64
Steps To Reproduce
- Create a Valtio proxy object with a numeric string as key:
import { useSnapshot, proxy } from 'valtio';
const state = proxy({ '12345': 'hello' });
2.In a ReactNative component, take a snapshot and log it when a button is pressed:
export const MyScreen: React.FC = () => {
const snap = useSnapshot(state);
return (
<Button
onPress={() => {
// Triggering iteration on the snapshot throws the error:
Object.entries(snap).forEach(([key, value]) => {
console.log(key, value);
});
}}
>
button for test
</Button>
);
}
- Run the application on a device/emulator with Hermes enabled.
- Press the button. The error occurs:
TypeError: ownKeys target is non-extensible but key is missing from trap result, js engine: hermes
The Expected Behavior
Using a numeric string as a key in a Valtio proxy should not lead to an error when accessing the snapshot (e.g., via Object.entries or direct logging). The behavior should be consistent with proxy objects that use non-numeric keys.
Additional context:
- Replacing the key with a non-numeric string (for example, proxy({ text: 'hello' })) avoids the error.
- A patch from this Valtio PR appears to work around the issue, suggesting the problem may be related to how Hermes handles Proxy traps.
- A similar issue is discussed in the Hermes issue #1063, though the root cause has not yet been identified.
Any guidance or fixes for this issue would be greatly appreciated. Thank you for your help!
Hi, thanks for the bug report! Unfortunately we are unable to investigate bug reports that require installing dependencies or building and running an entire RN app. Can you minimize the problem to something that can be reproduced standalone in the Hermes CLI?
@tmikov Thank you for your reply
I created a file named test.js and executed the following commands, which resulted in the same error
created file
'use strict';
// Proxy creation function
function createProxy(target) {
return new Proxy(target, {
// In ownKeys, exclude keys that are numeric strings
ownKeys(target) {
return Reflect.ownKeys(target).filter((key) => isNaN(Number(key)));
},
// getOwnPropertyDescriptor is required: returns the descriptor of the target object
getOwnPropertyDescriptor(target, key) {
return Object.getOwnPropertyDescriptor(target, key);
},
});
}
// --- Case with numeric string keys ---
// Make the target object frozen and non-extensible
const numericKeyTarget = Object.freeze({ 12345: 'hello' });
const numericKeyProxy = createProxy(numericKeyTarget);
print('[Case with numeric string keys]');
try {
// Attempting to get proxy keys with Object.keys
// In Hermes, this causes an error as the key "12345" is excluded from ownKeys results
print(Object.keys(numericKeyProxy));
} catch (e) {
print('Error:', e);
}
// --- Case with non-numeric string keys ---
// Similarly frozen target object but with key "text"
const nonNumericKeyTarget = Object.freeze({ text: 'hello' });
const nonNumericKeyProxy = createProxy(nonNumericKeyTarget);
print('\n[Case with non-numeric string keys]');
try {
// When converted to number, key becomes NaN so not filtered out - keys returned normally
print(Object.keys(nonNumericKeyProxy));
} catch (e) {
print('Error:', e);
}
command
bin/hermes -exec path/to/test.js
result log
[Case with numeric string keys]
Error: TypeError: ownKeys target key is non-configurable but not present in trap result
[Case with non-numeric string keys]
text
Thanks! When I run your repro with Hermes, v8 and JSC, I get similar results. What is the expected behavior?
$ hermes t.js
[Case with numeric string keys]
Error: TypeError: ownKeys target key is non-configurable but not present in trap result
[Case with non-numeric string keys]
text
$ v8 t.js
[Case with numeric string keys]
Error: TypeError: 'ownKeys' on proxy: trap result did not include '12345'
[Case with non-numeric string keys]
text
$ jsc t.js
[Case with numeric string keys]
Error: TypeError: Proxy object's 'target' has the non-configurable property '12345' that was not in the result from the 'ownKeys' trap
[Case with non-numeric string keys]
text
I found a reproducible code example. I encountered an issue when combining Proxy and Reflect.ownKeys on Hermes. The following code works on V8 but fails on Hermes:
if (typeof console === "undefined") {
globalThis.console = { log: print };
}
// Works on both Hermes and V8.
// const target = Object.preventExtensions({ foo: "bar" });
// Does not work on Hermes, but works on V8.
const target = Object.preventExtensions({ 123: "bar" });
const proxy = new Proxy(target, {
ownKeys(target) {
return Reflect.ownKeys(target);
},
});
console.log(Object.keys(proxy));
When the property key is a number (123 in this case), Hermes throws an error, whereas V8 correctly logs the keys. This discrepancy does not occur when the property key is a string (e.g., foo).
https://github.com/facebook/hermes/issues/1609#issuecomment-2653017237
I got results.
v8
$ node test.js
[ '123' ]
hermes
$ bin/hermes test.js
Uncaught TypeError: ownKeys target is non-extensible but key is missing from trap result
at keys (native)
at global (test.js:17:24)
@ichiki1023 Thank you, I was able to reproduce it with your example!
Is this problem solved? I have the same problem.
@tmikov
We are aware of the problem but haven't been able to work on it yet. Proxy is forbidden in React Native at Meta, because it is extremely slow, which makes it challenging to elevate this issue to high priority.