-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce stricter type for controller on change callback #10342
Enforce stricter type for controller on change callback #10342
Conversation
Open in CodeSandbox Web Editor | VS Code | VS Code Insiders |
thanks for the PR, hopefully, this would not break the user's app 🙏 I will add the update to the change log |
close #10466 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!Thanks.
Narrator: it did break the user's app. Unfortunately, I don't have time for a proper repro, but it did break our builds using Here's the error in case it can be useful.
Thanks for all the good work |
Perhaps we need to revert this as this probably brought breaking changes. |
definitely a breaking change, a good one, but still unexpected to see my code break because of a minor change |
at least from my understanding breaking change is when you change how it is working, or change how it needs to be used. In this place nothing is changed, it is more like it finally exposes issues with users code (mostly because onChange values mismatch FormValues), because previously it was any, and they could do whatever they want |
My case is similar to @AurelienGasser . I have a custom component that has a "clear" button and it triggers the Reverted to |
but what if you do form.watch? it will returns incorrect type that value is always defined and you get undefined errors. FormValues definition is not only used for submission. Typescript has no way of knowing that validator will catch undefined errors, so you either need to cast in your submit fn, or use something like Zod to parse values. |
For me, this change breaks all custom controlled components in my app where I restrict the field's value type using the type NumberInputFieldProps<
TFieldValues extends FieldValues,
TName extends FieldPathByValue<TFieldValues, number | undefined>,
> = NumberInputProps & UseControllerProps<TFieldValues, TName>;
function NumberInputField<
TFieldValues extends FieldValues,
TName extends FieldPathByValue<TFieldValues, number | undefined>,
>({ name, defaultValue, rules, control, ...inputProps }: NumberInputFieldProps<TFieldValues, TName>) {
const {
field: { value, onChange, onBlur: onFieldBlur, ref, ...fieldProps },
} = useController({
name,
defaultValue,
rules,
control,
});
const onChangeValue: ArkNumberInputProps['onChange'] = ({ valueAsNumber }) => onChange(valueAsNumber);
return (
<NumberInput
className={numberInput()}
{...inputProps}
{...fieldProps}
value={value}
onChange={onChangeValue}
>
<NumberInputScrubber />
<ArkNumberInputField ref={ref} />
<NumberInputControl>
<NumberInputIncrementTrigger>
<FiChevronUp />
</NumberInputIncrementTrigger>
<NumberInputDecrementTrigger>
<FiChevronDown />
</NumberInputDecrementTrigger>
</NumberInputControl>
</NumberInput>
);
} TS error:
|
this pattern seems to be broken by design. Yes, it enforces that from outside you can only pass correct |
@bajormar I don't think the pattern is broken. It is very useful to have a correctly typed API for the component. Yes, my component needs to handle |
What I mean by "broken" is that currently it is impossible from TypeScript perspective to gracefully handle inner Input controller typings. To have full power of typesafety, the best approach is to use
This way controllerProps is narrowed correctly. For simpler mapping I also have |
In our case this breaks compatibility with some MUI form components such as The type that MUI uses for their onChange is now not compatible with the new Full example:
https://codesandbox.io/s/dreamy-villani-wftdyf?file=/src/Select.tsx |
Yes this is definitely a breaking change, and should be reverted and reintroduced in a major version. I much prefer the previous behavior, as it allowed setting undefined/null/whatever and the form validation handles these as it should. |
So that is basically the same as to say "I prefer to have |
Having types is not bad. However, more care should be taken in how this may influence existing use cases and clearly this change must be a major version, as it is breaking to current workflows. Plus, the current type clearly doesn't work everywhere it should. You mentioned earlier that it is simply a case of bad code, however this is not true, since many actual features break due to this, including but not limited to: MUI compatibility and setting undefined |
it is not for me to decide what is included and what is not :D I am just giving reasons why this change improves existing library and also providing a solution how with 5 symbols you can get identical behaviour as before, but you are the one that do not want to listen |
My issue with this proposed solution is it would require me to write an onChange for my component. Currently the onChange is added by using |
Same here. I just use in a form that has 20 or so inputs, with different types etc, don't see myself upgrading to this new version any time soon. |
Hello @bajormar , I'm having an issue with TypeScript types when using Here's the import React from 'react';
import {Controller, UseControllerProps, FieldValues} from 'react-hook-form';
import {TextInput, TextInputProps} from '../TextInput/TextInput';
export function FormTextInput<T extends FieldValues>({
control,
name,
rules,
...textInputProps
}: TextInputProps & UseControllerProps<T>) {
return (
<Controller
control={control}
name={name}
rules={rules}
render={({field, fieldState}) => (
<TextInput
value={field.value}
onChangeText={field.onChange}
errorMessage={fieldState.error?.message}
{...textInputProps}
/>
)}
/>
);
} And the onChangeText type from TextInput: /**
* Callback that is called when the text input's text changes.
* Changed text is passed as an argument to the callback handler.
*/
onChangeText?: ((text: string) => void) | undefined; The onChange type from ControllerRenderProps is causing the issue: export type ControllerRenderProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = {
onChange: (event: ChangeEvent | FieldPathValue<TFieldValues, TName>) => void;
onBlur: Noop;
value: FieldPathValue<TFieldValues, TName>;
name: TName;
ref: RefCallBack;
}; Here's the TypeScript error I'm getting:
My {
"dependencies": {
"@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13",
"@shopify/restyle": "^2.4.2",
"react": "18.2.0",
"react-hook-form": "^7.45.0",
"react-native": "0.71.10",
"react-native-safe-area-context": "^4.6.2",
"react-native-screens": "^3.22.0",
"react-native-svg": "^13.9.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native-community/eslint-config": "^3.2.0",
"@tsconfig/react-native": "^2.0.2",
"@types/jest": "^29.2.1",
"@types/react": "^18.0.24",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",
"eslint": "^8.19.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.73.9",
"prettier": "^2.4.1",
"react-test-renderer": "18.2.0",
"typescript": "4.8.4"
},
} Can you help me an idea how to solve this type problem? |
This breaks most components libraries out there due to requiring a custom onChange, so does not really matter whether it's good or not it should be on a major release. |
the |
This PR is so simple, It doesn't cover all cases and all kinds of possible fields for form. You enforce stricter type for controller on change callback and force dev who uses generic types using as as a solution? |
It is open source project, you can create a PR which fixes your case. I tried to find a way how to make it type safe in other cases, but could not do this. I am not even sure if that is possible in TypeScript |
Can we just revert this asap? If not, atleast propose some good official website docs on how we should do things now. Clearly this breaks alot of situations where people rely on. |
I like strict types, but the change should be made in a major release since it is a breaking change. I encourage reverting and postponing this until the next major. |
Is it possible this change has also impacted components that utilise I have an abstraction of a MUI textfield component like such
I had a Reverting back to |
@JakeSaterlay I see the changes from this PR are only related to Typescript types (+ some tests), so that does not affect runtime code which could lead to things not working like you experience. So it's probably something different |
I had same issues with MUI components as @JakeSaterlay Maybe it's not related to the PR, but it's definitely related to the release. Reverting to the previous version helped. |
Would be great to re-create the problem in isolation and raise an issue @kvasnicaj I don't think I will get a chance to do this until the weekend though |
Whilst this is true, aside from putting an "as any" in every form component, most devs using MUI or other libraries are going to have to change their runtime code to fit around these changes. Are these examples tested with each "minor" release? |
We also have a problem with using We have built a generic form generator, which accepts the form data type as a generic: function Form<FormData extends FieldValues>({
...
} Inside case "checkbox":
return (
<Controller
{...controllerProps}
render={({ field: { value, onChange, onBlur } }) => {
return (
<Checkbox
{...commonProperties}
onChange={(checked) => handleOnChange(() => onChange(checked))}
onBlur={onBlur}
checked={value}
/>
);
}}
/>
); This has worked perfectly fine, but now I'm getting an error while calling
I don't understand what's wrong. The type should be |
@bajormar Thank you for improving this library! I'm all in on type safety, but would really appreciate a little more information on how to deal with the new stricter type checks introduced with this PR. A few examples for typical use cases in the change log might go a long way. I personally have a lot of type errors after upgrading to this revision and most of my use cases look somehow like this, with TypeScript complaining type PropsBaseType<TFieldValues extends FieldValues> = {
control: Control<TFieldValues>,
name: FieldPath<TFieldValues>,
disabled?: boolean,
placeholder?: string,
label: string,
helperText?: string,
errorText?: string,
autoFocus?: boolean,
required?: boolean,
};
type PropsTextType<TFieldValues extends FieldValues> = PropsBaseType<TFieldValues> & {
minLength?: ValidationRule<number>,
maxLength?: ValidationRule<number>,
pattern?: ValidationRule<RegExp>,
validate?: Validate<string, TFieldValues>,
};
export const InputText = <TFieldValues extends FieldValues>({
control,
name,
disabled = false,
label = '',
placeholder = '',
helperText = '',
autoFocus = false,
required = false,
minLength,
maxLength,
pattern,
validate,
}: PropsTextType<TFieldValues>): JSX.Element => {
const {field, fieldState} = useController({
control,
name,
rules: {
required,
minLength,
maxLength,
pattern,
validate,
},
});
const [value, setValue] = React.useState<string>(field.value);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
field.onChange(e.target.value);
setValue(e.target.value);
};
const fieldProps: TextFieldProps = {
disabled,
label,
placeholder,
error: Boolean(fieldState.error),
helperText: fieldState.error ? 'Error' : helperText,
autoFocus,
value,
name: field.name,
onChange: handleChange,
onBlur: field.onBlur,
inputRef: field.ref,
...elementDefaults,
};
return <TextField {...fieldProps}/>;
}; What would be type proper and type safe way to resolve this kind of type error? |
I wouldn't mind this fix, but the problem is that the type does not account for types that transform from one value to another. For example, your onChange might be a string that gets transformed into a number. With this change, I am unable to use transform and not have type issues. Keep that in mind if you release at a later version |
It's also an issue for cases in which there's validation, where the field cannot be null or undefined but the onChange can. |
I actually don't think keeping the current "any" behaviour is necesarrily a bad thing here. Just let each dev do whatever typing/parsing they want for this use case. |
I have a similar issue like @Jarzka with checkboxes + shadcn-ui (zod + react-hook-form) : |
This kind of change should have been a breaking change.. not Minor.. |
When using Controller field onChange fn, you were able to pass any value you want (because it was typed as any), even when it mismatches the type defined on the form.
So if form has defined type
You could pass
This is a huge typesafety hole, and I have patches my projects to have such behaviour. After this change a lot of error were exposed.
This implementation is still not 100% typesafe, because it still accepts event, for backwards compatibility reasons. But when event is passed, it suffer from safe issue - types can mismatch.
Ideally I would opt to either remove option to pass event at all (but this would hurt DX), or provide additional typesafe onChange which allows to pass just the exact type value