diff --git a/docs/framework/react/guides/reusable-styled-form-component.md b/docs/framework/react/guides/reusable-styled-form-component.md new file mode 100644 index 000000000..837452f9e --- /dev/null +++ b/docs/framework/react/guides/reusable-styled-form-component.md @@ -0,0 +1,321 @@ +--- +id: reusable-styled-form-component +title: Reusable Styled Form Component +--- + +# Creating your own Reusable Styled Form Component +This guide will walk you through the process of creating a reusable styled form component. A reusable styled form component is a component that encapsulates the logic and styling of a form, allowing you to reuse it across your application. It is highly opinionated and meant to be customised for your specific project. The concepts in this guide are applicable to any UI library and it is meant as a reference for you to create your own reusable styled form component. + +You can think of a reusable styled form component as a wrapper around TanStack Form that encapsulates the logic and styling of a form for a higher level of abstraction. It can be thought of as a giant UI component for forms that implements simpler UI components like `Input`, `Select`, `Button`, etc. + +## When to Create a Reusable Styled Form Component +Creating a reusable styled form component is a great way to reduce boilerplate and enforce consistency across your application. It is especially useful if you have a lot of forms in your application, but in many cases it is overkill. If you only have a few forms in your application, it is probably better to use TanStack Form directly. + +## Prerequisites +This example uses [Tailwind CSS](https://tailwindcss.com/) for styling and [Class Variance Authority](https://beta.cva.style/) for UI component variants. You can use any styling solution of your choice, just replace the custom components with your own and provide the appropriate props. It also uses `Zod` for validation, but you can use any validation library of your choice. If you want to take a look at the custom UI components you can see them in the codesandbox in the examples section. + +## Composition +The reusable form component is composed of compound components to make it easier to customise. The `
` component is the main component. While `` and `` renders the appropriate components from TanStack Form. The rest of the compound components are examples of components that you can add to your own reusable form component. For example, `` is a compound component that renders a text field. You can add as many compound components as you want to your reusable form component and some of the examples in this guide are just that, examples. You may not need a `` component, but it is included here. All the components in this example are forwarding the ref to the underlying HTML element. This is so that you can access the HTML element directly if needed. For example, if you want to focus the first field in the form when the form is submitted. + +## Let's Get Started + +### `` + +```tsx +const Form = React.forwardRef( + ({ className, children, formOptions, ...props }, ref) => { + const { Provider, Field, handleSubmit, useStore, Subscribe, reset } = + useForm({ + validatorAdapter: zodValidator, + ...formOptions, + }) + return ( + + { + e.preventDefault() + e.stopPropagation() + handleSubmit() + }} + ref={ref} + {...props} + > + + {typeof children === 'function' ? children(useStore) : children} + + + + ) + }, +) as FormComponentProps +``` +The `
` component wraps the form in the `` from TanStack Form and provides the appropriate `onSubmit` props which is something you would normally do when using TanStack Form directly. It also initiates the `useForm` from Tanstack Form and here you can pass any options that you want to have in every single form in your application. For example the validator adapter. The `` component also renders the `` which provides the `` and `` components to the rest of the form. The `` component also provides the `useStore` hook from TanStack Form to the rest of the form. This is so that you can access the form state from anywhere in the form. + +### `` & `` + +```tsx +Form.Field = React.forwardRef(({ ...props }, ref) => { + const { Field } = React.useContext(FormContext) + return +}) + +Form.Subscribe = React.forwardRef(({ ...props }, ref) => { + const { Subscribe } = React.useContext(FormContext) + return +}) +``` +The `` and `` components are just wrappers around the `` and `` components from TanStack Form. They are not necessary, but they make it easier to customise the form. For example, `` gives access to the `field` prop which is the field object from TanStack Form. This allows you to access and overwrite any value that you pass down in your UI compound components. For example, you can have a default `onChange={(e) => field.handleChange(e.target.value)}` prop that you want to change to something else like `onChange={(e) => field.handleChange(e.target.valueAsNumber)}` where you still would need access to the `field` prop. The `` component can be useful if you want to render a component that is not a field, but still needs access to the form state. For example, a loading indicator, a button that is disabled when the form is submitting, a JSON representation of the form state, etc. If you don't need this flexibility in your reusable form component, you can hardcode the `` and `` components from TanStack Form directly into your UI compound components. + +### ``, `` & `` + +```tsx +Form.Field.Text = React.forwardRef( + ({ className, hasErrorField, field, label, ...props }, ref) => { + return ( +
+ field.handleChange(e.target.value)} + aria-describedby={ + hasErrorField && field.state.meta.errors.length !== 0 + ? `${field.name}-error` + : '' + } + aria-invalid={field.state.meta.errors.length !== 0} + ref={ref} + {...props} + /> + {label && ( + + )} +
+ ) + }, +) + +Form.Field.Checkbox = React.forwardRef< + HTMLInputElement, + FormFieldCheckboxProps +>(({ className, children, field, label, ...props }, ref) => { + return ( +
+ field.handleChange(e.target.checked)} + ref={ref} + {...props} + /> + {label && ( + + )} +
+ ) +}) + +Form.Field.Select = React.forwardRef( + ({ className, hasErrorField, field, label, ...props }, ref) => { + return ( +
+ {label && ( + + )} + `, `
+ ) + }, +) + +Form.Field.Text.displayName = 'Form.Field.Text' + +Form.Field.Checkbox = React.forwardRef< + HTMLInputElement, + FormFieldCheckboxProps +>(({ className, children, field, label, ...props }, ref) => { + return ( +
+ field.handleChange(e.target.checked)} + ref={ref} + {...props} + /> + {label && ( + + )} +
+ ) +}) + +Form.Field.Checkbox.displayName = 'Form.Field.Checkbox' + +Form.Field.Select = React.forwardRef( + ({ className, hasErrorField, field, label, ...props }, ref) => { + return ( +
+ {label && ( + + )} + + ) + }, +) + +Input.displayName = 'Input' + +export { Input, inputVariants, type InputProps } diff --git a/examples/react/reusable-styled-form-component/src/Label.tsx b/examples/react/reusable-styled-form-component/src/Label.tsx new file mode 100644 index 000000000..95ac71f7b --- /dev/null +++ b/examples/react/reusable-styled-form-component/src/Label.tsx @@ -0,0 +1,25 @@ +import * as React from 'react' +import { cx } from './utils' + +type LabelProps = React.LabelHTMLAttributes & { + className?: string +} + +const Label = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +