Repository: vazco/uniforms (ts)

Issue:
[QUESTION]: Can't convert string to object
Hi, I seem to be experiencing a weird issue.

I am using a GraphQL schema with my form and I have a field that is a String type.
In my model, I load the string with a value.
I have a Select that is a custom component that allows me to select a different value and the selected value returned is an object.

When I change the selection the field in the model is updated correctly from a string to an object, and in the transform I can change it when I submit my form.

The issue I am having is when I use the formRef that I passed to AutoForm, and call onChange to change my field value to an object, it gives me this error: 

> Cannot use 'in' operator to search for 'name' in some string

It might be related to: https://github.com/vazco/uniforms/issues/960

You can see an example here:

https://codesandbox.io/s/cant-convert-string-to-object-with-onchange-o1o7q

I am using latest uniforms version.

I am triggering the change with `useEffect` in the code on line `116`.

In this example, I did not put a Select as the field, I simply wanted to show the error message.

Hope you can help.
Let me know if I am not being clear in my description.

Thanks!



Pull request description:
Added support for changing value type in changedKeys (fixes #982).
added typeOf valueA and valueB for changedKeys

PR for https://github.com/vazco/uniforms/issues/982#issuecomment-898503298


Patch:
diff --git a/packages/uniforms/src/changedKeys.ts b/packages/uniforms/src/changedKeys.ts
index c293d534..26da23d9 100644
--- a/packages/uniforms/src/changedKeys.ts
+++ b/packages/uniforms/src/changedKeys.ts
@@ -2,14 +2,18 @@ import isEqual from 'lodash/isEqual';
 
 import { joinName } from './joinName';
 
+function isObject(value: unknown): value is Record<string, unknown> {
+  return !!value && value === Object(value) && !(value instanceof Date);
+}
+
 // eslint-disable-next-line complexity
-export function changedKeys<T>(root: string, valueA?: T, valueB?: T) {
-  if (!valueA || valueA !== Object(valueA) || valueA instanceof Date) {
+export function changedKeys(root: string, valueA?: unknown, valueB?: unknown) {
+  if (!isObject(valueA) || (valueB && typeof valueA !== typeof valueB)) {
     return isEqual(valueA, valueB) ? [] : [root];
   }
 
   const changed = [root];
-  if (valueB) {
+  if (isObject(valueB)) {
     for (const key in valueA) {
       if (!(key in valueB) || !isEqual(valueA[key], valueB[key])) {
         changed.push(joinName(root, key));


Test patch:
diff --git a/packages/uniforms/__tests__/changedKeys.ts b/packages/uniforms/__tests__/changedKeys.ts
index 0002969b..80e2612f 100644
--- a/packages/uniforms/__tests__/changedKeys.ts
+++ b/packages/uniforms/__tests__/changedKeys.ts
@@ -95,4 +95,21 @@ describe('changedKeys', () => {
       expect(changedKeys('a', 'pe', 'no')).toEqual(['a']);
     });
   });
+
+  it('works with changing value types', () => {
+    expect(changedKeys('a', '1', 1)).toEqual(['a']);
+    expect(changedKeys('a', 1, '1')).toEqual(['a']);
+    expect(changedKeys('a', 'true', true)).toEqual(['a']);
+    expect(changedKeys('a', true, 'true')).toEqual(['a']);
+    expect(changedKeys('a', 'false', false)).toEqual(['a']);
+    expect(changedKeys('a', false, 'false')).toEqual(['a']);
+    expect(changedKeys('a', '0', null)).toEqual(['a']);
+    expect(changedKeys('a', null, '0')).toEqual(['a']);
+    expect(changedKeys('a', Date.now(), new Date())).toEqual(['a']);
+    expect(changedKeys('a', new Date(), Date.now())).toEqual(['a']);
+    expect(changedKeys('a', 'value', { key: 'value' })).toEqual(['a']);
+    expect(changedKeys('a', { key: 'value' }, 'value')).toEqual(['a']);
+    expect(changedKeys('a', 'value', ['value'])).toEqual(['a']);
+    expect(changedKeys('a', ['value'], 'value')).toEqual(['a']);
+  });
 });