-
Notifications
You must be signed in to change notification settings - Fork 4
Using TypedController for nested input and useFormContext #14
Comments
@Amiryy Hello, Thanks for your feedback 👍 const TypedController = useTypedController<FormValues>({}); You don't have to create a NestedInput. import { useTypedController } from '@hookform/strictly-typed';
import { useForm, FormProvider } from 'react-hook-form';
interface FormValues {
flat: string;
count: number;
letters: {
a: string,
b: number
}
nested: {
object: { test: string };
array: { test: boolean }[];
};
};
function App() {
const form = useForm<FormValues>();
const TypedController = useTypedController<FormValues>({});
const onSubmit = form.handleSubmit((data) => console.log(data));
return (
<FormProvider {...form}>
<form onSubmit={onSubmit}>
<TypedController
name="flat"
defaultValue=""
render={(props) => <TextField {...props} />}
/>
<TypedController
as="textarea"
name={['nested', 'object', 'test']}
defaultValue=""
rules={{ required: true }}
/>
<TypedController
name={['nested', 'array', 0, 'test']}
defaultValue={false}
render={(props) => <Checkbox {...props} />}
/>
{/* ❌: Type '"notExists"' is not assignable to type 'DeepPath<FormValues, "notExists">'. */}
<TypedController as="input" name="notExists" defaultValue="" />
{/* ❌: Type 'number' is not assignable to type 'string | undefined'. */}
<TypedController
as="input"
name={['nested', 'object', 0, 'notExists']}
defaultValue=""
/>
{/* ❌: Type 'true' is not assignable to type 'string | undefined'. */}
<TypedController as="input" name="flat" defaultValue={true} />
<input type="submit" />
</form>
</FormProvider>
);
} |
@kotarella1110 Thank you for the reply.
instead of just using a reusable wrapper that is rendering a
You didn't demonstrate it in any way. In what way does it support and how can it help me here? The code you shared is from the example, I couldn't find any special usage there. Thanks. |
@Amiryy I have two suggestions:
import React from "react";
import { TextField } from "@material-ui/core";
import { useTypedController } from "@hookform/strictly-typed";
import { DeepPath, DeepPathValue } from "@hookform/strictly-typed/dist/types";
type FormInputProps<
TFieldValues extends Record<string, any>,
TFieldName extends DeepPath<TFieldValues, TFieldName>
> = {
name: TFieldName;
defaultValue: DeepPathValue<TFieldValues, TFieldName>;
};
export const createTypedTextField = <
TFieldValues extends Record<string, any>
>() => {
const TypedTextField = <
UFieldValues extends TFieldValues,
TFieldName extends DeepPath<UFieldValues, TFieldName>
>({
name,
defaultValue
}: FormInputProps<UFieldValues, TFieldName>) => {
const TypedController = useTypedController<TFieldValues>({});
return (
<TypedController
name={name}
defaultValue={defaultValue}
render={(props) => <TextField {...props} />}
/>
);
};
return TypedTextField;
};
import React from "react";
import { TextField } from "@material-ui/core";
import { useTypedController } from "@hookform/strictly-typed";
import {
DeepPath,
DeepPathValue,
UnpackNestedValue,
FieldValuesFromControl
} from "@hookform/strictly-typed/dist/types";
import { Control } from "react-hook-form";
type FormInputProps<
TFieldValues extends Record<string, any>,
TFieldName extends DeepPath<TFieldValues, TFieldName>,
TControl extends Control
> = {
name: TFieldName;
defaultValue: DeepPathValue<TFieldValues, TFieldName>;
control: TControl;
};
const TypedTextField = <
TFieldValues extends UnpackNestedValue<FieldValuesFromControl<TControl>>,
TFieldName extends DeepPath<TFieldValues, TFieldName>,
TControl extends Control
>({
name,
defaultValue,
control
}: FormInputProps<TFieldValues, TFieldName, TControl>) => {
const TypedController = useTypedController({ control });
return (
<TypedController
name={name}
defaultValue={defaultValue}
render={(props) => <TextField {...props} />}
/>
);
};
export default TypedTextField; CodeSandbox: https://codesandbox.io/s/react-hook-form-usetypedcontroller-14-lvv9r Does this answer your questions? |
@kotarella1110 Thank you for your answer, I highly appreciate the effort. Eventually I came up with this structure:
which can be used like this:
I've updated my previous example with this working solution: Thanks! |
I posted an alternative solution for deeply nested structures in case it helps: https://stackblitz.com/edit/react-hook-form-strictly-typed?file=index.tsx |
Hello,
I'm trying to use a TypedController inside my nested input components combined with
useFormContext
.I want to maintain the type-safety of name and defaultValue props, using the example from the README of this repository as a basis.
Here's my working reproduction of the issue: stackblitz/Amiryy/react-hook-form-strictly-typed-poc
So I've composed a
NestedInput
component:To make TS accept my code, I had to use imported types such as
DeepPath
andDeepPathValue
for myname
anddefaultValue
props.As you can see in the snippet above - the second argument of my generic
NestedInputProps
isPath
, which for my understanding represents the path to a nested (or not) property in my form's object type. This type is then used forname
prop to match TypedController'sname
prop's type.Once I've tried to use my
NestedInput
inside a form I couldn't avoid having to pass thePath
argument:It seems like when using
<TypedController />
directly the types ofname
anddefaultValue
are inferred by their values, so when passing thename
prop a property's path, makes defaultValue expect a corresponding type of the property's value.(from the code example):
And so my bottom-line question is... how can I maintain this usability when passing down
name
anddefaultValue
props to a nested<TypedController />
?Thanks!
The text was updated successfully, but these errors were encountered: