Skip to content
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

Make Form generic for better TS support #5867

Merged
merged 9 commits into from
Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@babel/runtime-corejs3": "7.16.7",
"core-js": "3.23.3",
"pascalcase": "1.0.0",
"react-hook-form": "7.33.0"
"react-hook-form": "7.33.1"
},
"devDependencies": {
"@babel/cli": "7.16.7",
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/FormError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface ServerError extends Error {
result: Record<string, any>
}

interface RWGqlError {
export interface RWGqlError {
message: string
graphQLErrors: ReadonlyArray<GraphQLError>
networkError: Error | ServerParseError | ServerError | null
Expand Down
101 changes: 56 additions & 45 deletions packages/forms/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import {
UseFormProps,
} from 'react-hook-form'

import FormError from './FormError'
import FormError, { RWGqlError } from './FormError'

/**
* We slightly extend `react-hook-form`'s `RegisterOptions` to make working with GraphQL easier.
Expand Down Expand Up @@ -484,19 +484,20 @@ interface ServerErrorsContextProps {

const ServerErrorsContext = React.createContext({} as ServerErrorsContextProps)

export interface FormProps
export interface FormProps<TFieldValues>
extends Omit<React.ComponentPropsWithRef<'form'>, 'onSubmit'> {
error?: any
/**
* The methods returned by `useForm`.
* This props's only necessary if you've called `useForm` yourself to get access to one of it's functions, like `reset`.
* This prop is only necessary if you've called `useForm` yourself to get
* access to one of its functions, like `reset`.
*
* @example
*
* ```javascript
* const formMethods = useForm()
* ```typescript
* const formMethods = useForm<FormData>()
*
* const onSubmit = (data) => {
* const onSubmit = (data: FormData) => {
* sendDataToServer(data)
* formMethods.reset()
* }
Expand All @@ -506,7 +507,7 @@ export interface FormProps
* )
* ```
*/
formMethods?: UseFormReturn
formMethods?: UseFormReturn<TFieldValues>
/**
* Configures how React Hook Form performs validation, among other things.
*
Expand All @@ -518,50 +519,59 @@ export interface FormProps
*
* @see {@link https://react-hook-form.com/api/useform}
*/
config?: UseFormProps
onSubmit?: (
value: Record<string, any>,
event?: React.BaseSyntheticEvent
) => void
config?: UseFormProps<TFieldValues>
onSubmit?: (value: TFieldValues, event?: React.BaseSyntheticEvent) => void
}

/**
* Renders a `<form>` with the required context.
*/
const Form = forwardRef(
(
{
config,
error: errorProps,
formMethods: propFormMethods,
onSubmit,
children,
...rest
}: FormProps,
ref: ForwardedRef<HTMLFormElement>
) => {
const hookFormMethods = useForm(config)
const formMethods = propFormMethods || hookFormMethods
function FormInner<TFieldValues>(
{
config,
error: errorProps,
formMethods: propFormMethods,
onSubmit,
children,
...rest
}: FormProps<TFieldValues>,
ref: ForwardedRef<HTMLFormElement>
) {
const hookFormMethods = useForm<TFieldValues>(config)
const formMethods = propFormMethods || hookFormMethods

return (
<form
ref={ref}
{...rest}
onSubmit={formMethods.handleSubmit((data, event) =>
onSubmit?.(data, event)
)}
return (
<form
ref={ref}
{...rest}
onSubmit={formMethods.handleSubmit((data, event) =>
onSubmit?.(data, event)
)}
>
<ServerErrorsContext.Provider
value={
errorProps?.graphQLErrors[0]?.extensions?.properties?.messages || {}
}
>
<ServerErrorsContext.Provider
value={
errorProps?.graphQLErrors[0]?.extensions?.properties?.messages || {}
}
>
<FormProvider {...formMethods}>{children}</FormProvider>
</ServerErrorsContext.Provider>
</form>
)
}
)
<FormProvider {...formMethods}>{children}</FormProvider>
</ServerErrorsContext.Provider>
</form>
)
}

// Sorry about the `as` type assertion (type cast) here. Normally I'd redeclare
// forwardRef to only return a plain function, allowing us to use TypeScript's
// Higher-order Function Type Inference. But that gives us problems with the
// ForwardRefExoticComponent type we use for our InputComponents. So instead
// of changing that type (because it's correct) I use a type assertion here.
// forwardRef is notoriously difficult to use with UI component libs.
// Chakra-ui also says:
Tobbe marked this conversation as resolved.
Show resolved Hide resolved
// > To be honest, the forwardRef type is quite complex [...] I'd recommend
// > that you cast the type
// https://github.com/chakra-ui/chakra-ui/issues/4528#issuecomment-902566185
const Form = forwardRef(FormInner) as <TFieldValues>(
props: FormProps<TFieldValues> & React.RefAttributes<HTMLFormElement>
) => React.ReactElement | null

export interface LabelProps
extends Pick<FieldProps, 'errorClassName' | 'errorStyle'>,
Expand Down Expand Up @@ -621,7 +631,7 @@ const DEFAULT_MESSAGES = {
*
* @example Displaying a validation error message with `<FieldError>`
*
* `<FieldError>` doesnt render (i.e. returns `null`) when there's no error on `<TextField>`.
* `<FieldError>` doesn't render (i.e. returns `null`) when there's no error on `<TextField>`.
*
* ```jsx
* <Label name="name" errorClassName="error">
Expand Down Expand Up @@ -1002,6 +1012,7 @@ export {
Form,
ServerErrorsContext,
FormError,
RWGqlError,
FieldError,
InputField,
Label,
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6206,7 +6206,7 @@ __metadata:
pascalcase: 1.0.0
react: 17.0.2
react-dom: 17.0.2
react-hook-form: 7.33.0
react-hook-form: 7.33.1
typescript: 4.7.3
peerDependencies:
graphql: 16.5.0
Expand Down Expand Up @@ -25869,12 +25869,12 @@ __metadata:
languageName: node
linkType: hard

"react-hook-form@npm:7.33.0":
version: 7.33.0
resolution: "react-hook-form@npm:7.33.0"
"react-hook-form@npm:7.33.1":
version: 7.33.1
resolution: "react-hook-form@npm:7.33.1"
peerDependencies:
react: ^16.8.0 || ^17 || ^18
checksum: 86555a461186e3c03116c215229fa8a7831f28e1238184f12b5fc840c429c56e2a2976be06f68506d30759438622ebbfc6498c91556e1633b1ae4d500b0bc3f1
checksum: 6380700d877604be4326c845599240a42bb4eb31109ff47cd6aa9e567a906c9fe512c965396a72c6a0e52a4992922226ea58cf8bfb43d6ebbaa08972e8be4c65
languageName: node
linkType: hard

Expand Down