Typesafe input component
In the guide on scoping, we showed how to create a simple, typesafe text input component. However, there are many different types of inputs and those can support different types of values.
This recipe shows how to create a typesafe input component that supports all the different types of inputs.
Features
Our input component will have the following features:
- Accepts a
scope
prop that is typesafe for the inputtype
. - Automatically displays its error message if there is one.
- Sets the correct
aria-*
attributes - Can accept all the props normally accepted by the native input element.
Preview
One component for different input types
Recipe
import { useField, FormScope, ValueOfInputType } from "@rvf/react";
import { ComponentPropsWithRef, forwardRef, useId } from "react";
// For our props, we'll take everything from the native input element except for `type`.
// You can make futher changes here to suite your needs.
type BaseInputProps = Omit<ComponentPropsWithRef<"input">, "type">;
interface MyInputProps<Type extends string> extends BaseInputProps {
label: string;
type?: Type;
scope: FormScope<ValueOfInputType<Type>>;
}
// We need to do this in order to get a generic type out of `forwardRef`.
// In React 19, you won't need this anymore.
type InputType = <Type extends string>(
props: MyInputProps<Type>,
) => React.ReactNode;
const MyInputImpl = forwardRef<HTMLInputElement, MyInputProps<string>>(
({ label, scope, type, ...rest }, ref) => {
const field = useField(scope);
const inputId = useId();
const errorId = useId();
return (
<div className="myInputStyles">
<label htmlFor={inputId}>{label}</label>
<input
{...field.getInputProps({
type,
id: inputId,
ref,
"aria-describedby": errorId,
"aria-invalid": !!field.error(),
...rest,
})}
/>
{field.error() && <p id={errorId}>{field.error()}</p>}
</div>
);
},
);
MyInputImpl.displayName = "MyInput";
export const MyInput = MyInputImpl as InputType;