react-hook-form: FormState Proxy is incomplete and crashes Jest snapshot serializer
Describe the bug
The Proxy object returned for formState is incomplete. It returns {} for any unknown property which breaks expected behaviour in applications.
An example where this is hugely problematic is in Jest snapshots. Various snapshot serializers match on the formState object when trying to serialize is because they believe that the object has the property they need. Tests end up crashing. This is the sympton I experienced after I upgraded react-hook-form to the latest version.
Here’s an example that demonstrates the unexpected behaviour:
const proxy = new Proxy({ a: 1 }, {
get: (obj, prop) {
if (prop in obj) {
return obj[prop];
}
return {};
},
});
console.log(proxy.bla); // prints `{}`, which should be `undefined`
console.log('bla' in proxy);` // prints true, which should be false
To Reproduce Steps to reproduce the behavior:
- Create a Jest snapshot test using
useForm():
import React from 'react';
import { useForm } from 'react-hook-form';
import ShallowRenderer from 'react-test-renderer/shallow';
test('that it works', () => {
const OtherComponent = () => null;
const Component = () => {
const form = useForm();
return <OtherComponent form={form} />;
};
const renderer = new ShallowRenderer();
renderer.render(<Component />);
expect(renderer.getRenderOutput()).toMatchSnapshot(); // this will crash
});
Expected behavior
The Proxy object should implement the trap for the in operator and return undefined for unknown properties.
Additional context
One could argue this is a bug in the Jest snapshot serializer and that it should detect this kind of behaviour. However, one the fundamental concepts behind Proxy objects is that they cannot easily be detected and behave like normal objects. The formState proxy object not behaving as a normal object deviates from that concept.
Workaround For anyone experiencing the same problem, I’ve temporarily installed a custom Jest serializer that detects proxy objects and clones them into normal objects:
import util from 'util';
import { cloneDeep } from 'lodash';
class ProxyObjectSerializer {
test(value) {
return util.types.isProxy(value);
}
print(value, serialize) {
return serialize(cloneDeep(value));
}
}
expect.addSnapshotSerializer(new ProxyObjectSerializer());
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 16 (16 by maintainers)
Commits related to this issue
- 🐞 fix react-hook-form#1520 return proper proxy object — committed to SectorLabs/react-hook-form by Photonios 4 years ago
- ð fix react-hook-form#1520 return proper proxy object — committed to SectorLabs/react-hook-form by Photonios 4 years ago
- 🐞 fix react-hook-form#1520 return proper proxy object — committed to SectorLabs/react-hook-form by Photonios 4 years ago
now that’s much better! my concern is over. Thanks for understanding, because 99% of users’ probably won’t need this fix. The same thing that I talked to @kotarella1110 if I can save 0.01% of perf for 99% users I will defend for them.
As I’ve mentioned in another comment, I am up for removing the extra
hastrap I added in my PR and just returningundefinedfor unknown objects.I understand your concerns with adding bloat for an edge case.
That would make the change a one liner. If not for the sake of fixing Jest snapshots, then for the sake of making the behaviour consistent between with/without proxy.
I’ll update my PR to remove the extra
hastrap.EDIT: Done. I’ve updated the PR to only change the return value of the proxy object.
I will lave this issue open for sometimes, and let’s hear what’s the community/users’ feedback. Over time, if a lot of people encounter the issue with this
proxy, then we will merge the PR (or at least improve that PR so it’s not running each hook’s invoke/re-render).Temp solution: if you having an issue with
Proxywith your unit test (which i don’t) you can always disable Proxy during the test.Another good reason to fix is the fact that behavior is inconsistent between using react-hook-form in an environment in which
Proxyis available and in an environment where it is. IfProxyis not available,react-hook-formreturns theformStateas a normal object. In which caseform.formState.HELLOreturnsundefined.Why is it intended to be like this? What practical purpose does it serve?
It does. The codesandbox link you posted in a comment on the PR I opened: https://codesandbox.io/s/strange-shirley-pdrhr?file=/src/app.test.js
The sandbox is a poor example because the
Proxydoes not get activated.window.Proxyisundefined. Which proves my point. Behaviour changes depending on whether the Proxy is used or not. With a quick trick, we can demonstrate that the test fails when using react-hook-from v5.6.0:https://codesandbox.io/s/elastic-khorana-2o27t?file=/src/app.test.js
If we try the same thing with the beta you released, it passes:
https://codesandbox.io/s/eloquent-night-q0wcb?file=/src/app.test.js
Even if it is, there’s still good motivation to fix it. Jest is almost the de-factor standard when it comes to testing React components. It is very widespread. react-hook-form suddenly breaking compatibility with it is reason enough to fix it IMHO. Then there’s also the issue of inconsistency in behaviour depending on whether Proxy is used or not.