Skip to content

Commit

Permalink
docs(input): form validation examples
Browse files Browse the repository at this point in the history
  • Loading branch information
Peterl561 committed Dec 15, 2024
1 parent 69d400b commit a56533c
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 0 deletions.
40 changes: 40 additions & 0 deletions apps/docs/content/components/input/built-in-validation.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {Button, Form, Input} from "@nextui-org/react";

export default function App() {
const [submitted, setSubmitted] = React.useState(null);

const onSubmit = (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));

setSubmitted(data);
};

return (
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
<Input
isRequired
errorMessage={({validationDetails, validationErrors}) => {
if (validationDetails.typeMismatch) {
return "Please enter a valid email address";
}

return validationErrors;
}}
label="Email"
labelPlacement="outside"
name="email"
placeholder="Enter your email"
type="email"
/>
<Button color="primary" type="submit">
Submit
</Button>
{submitted && (
<div className="text-small text-default-500">
You submitted: <code>{JSON.stringify(submitted)}</code>
</div>
)}
</Form>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input/built-in-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./built-in-validation.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
40 changes: 40 additions & 0 deletions apps/docs/content/components/input/custom-validation.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {Button, Form, Input} from "@nextui-org/react";

export default function App() {
const [submitted, setSubmitted] = React.useState(null);

const onSubmit = (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));

setSubmitted(data);
};

return (
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
<Input
isRequired
label="Username"
labelPlacement="outside"
name="username"
placeholder="Enter your username"
type="text"
validate={(value) => {
if (value.length < 3) {
return "Username must be at least 3 characters long";
}

return value === "admin" ? "Nice try!" : null;
}}
/>
<Button color="primary" type="submit">
Submit
</Button>
{submitted && (
<div className="text-small text-default-500">
You submitted: <code>{JSON.stringify(submitted)}</code>
</div>
)}
</Form>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input/custom-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./custom-validation.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
8 changes: 8 additions & 0 deletions apps/docs/content/components/input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import startEndContent from "./start-end-content";
import errorMessage from "./error-message";
import regexValidation from "./regex-validation";
import controlled from "./controlled";
import builtInValidation from "./built-in-validation";
import customValidation from "./custom-validation";
import realTimeValidation from "./real-time-validation";
import serverValidation from "./server-validation";
import customStyles from "./custom-styles";
import customImpl from "./custom-impl";

Expand All @@ -34,6 +38,10 @@ export const inputContent = {
errorMessage,
regexValidation,
controlled,
builtInValidation,
customValidation,
realTimeValidation,
serverValidation,
customStyles,
customImpl,
};
53 changes: 53 additions & 0 deletions apps/docs/content/components/input/real-time-validation.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {Button, Form, Input} from "@nextui-org/react";

export default function App() {
const [submitted, setSubmitted] = React.useState(null);
const [password, setPassword] = React.useState("");
const errors = [];

const onSubmit = (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));

setSubmitted(data);
};

if (password.length < 4) {
errors.push("Password must be 4 characters or more.");
}
if ((password.match(/[A-Z]/g) || []).length < 1) {
errors.push("Password must include at least 1 upper case letter");
}
if ((password.match(/[^a-z0-9]/gi) || []).length < 1) {
errors.push("Password must include at least 1 symbol.");
}

return (
<Form className="w-full max-w-xs" validationBehavior="native" onSubmit={onSubmit}>
<Input
errorMessage={() => (
<ul>
{errors.map((error, i) => (
<li key={i}>{error}</li>
))}
</ul>
)}
isInvalid={errors.length > 0}
label="Password"
labelPlacement="outside"
name="password"
placeholder="Enter your password"
value={password}
onValueChange={setPassword}
/>
<Button color="primary" type="submit">
Submit
</Button>
{submitted && (
<div className="text-small text-default-500">
You submitted: <code>{JSON.stringify(submitted)}</code>
</div>
)}
</Form>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input/real-time-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./real-time-validation.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
49 changes: 49 additions & 0 deletions apps/docs/content/components/input/server-validation.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {Button, Form, Input} from "@nextui-org/react";

export default function App() {
const [isLoading, setIsLoading] = React.useState(false);
const [errors, setErrors] = React.useState({});

const onSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);

const data = Object.fromEntries(new FormData(e.currentTarget));
const result = await callServer(data);

setErrors(result.errors);
setIsLoading(false);
};

return (
<Form
className="w-full max-w-xs"
validationBehavior="native"
validationErrors={errors}
onSubmit={onSubmit}
>
<Input
isRequired
isDisabled={isLoading}
label="Username"
labelPlacement="outside"
name="username"
placeholder="Enter your username"
/>
<Button color="primary" isLoading={isLoading} type="submit">
Submit
</Button>
</Form>
);
}

// Fake server used in this example.
async function callServer(_) {
await new Promise((resolve) => setTimeout(resolve, 500));

return {
errors: {
username: "Sorry, this username is taken.",
},
};
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input/server-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./server-validation.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
61 changes: 61 additions & 0 deletions apps/docs/content/docs/components/input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,43 @@ You can use the `value` and `onValueChange` properties to control the input valu
> **Note**: NextUI `Input` also supports native events like `onChange`, useful for form libraries
> such as [Formik](https://formik.org/) and [React Hook Form](https://react-hook-form.com/).
### With Form

`Input` can be used with a `Form` component to leverage form state management. By default, `Form` components use `validationBehavior="aria"`, which will not block form submission if any inputs are invalid. For more on form and validation behaviors, see the [Forms](/docs/guide/forms) guide.

#### Built-in Validation

`Input` supports the following [native HTML constraints](https://developer.mozilla.org/docs/Web/HTML/Constraint_validation):

- `isRequired` indicates that a field must have a value before the form can be submitted.
- `minLength` and `maxLength` specify the minimum and length of text input.
- `pattern` provides a custom regular expression that a text input must conform to.
- `type="email"` and `type="url"` provide built-in validation for email addresses and URLs.

When using native validation, error messages can be customized by passing a function to `errorMessage` and checking the [ValidityState](https://developer.mozilla.org/docs/Web/API/ValidityState) of `validationDetails`.

<CodeDemo title="Built-in Validation" files={inputContent.builtInValidation} />

#### Custom Validation

In addition to built-in constraints, you can provide a function to the `validate` property for custom validation.

<CodeDemo title="Custom Validation" files={inputContent.customValidation} />

#### Realtime Validation

If you want to display validation errors while the user is typing, you can control the field value and use the `isInvalid` prop along with the `errorMessage` prop.

<CodeDemo title="Realtime Validation" files={inputContent.realTimeValidation} />

#### Server Validation

Client-side validation provides immediate feedback, but you should also validate data on the server to ensure accuracy and security.
NextUI allows you to display server-side validation errors by using the `validationErrors` prop in the `Form` component.
This prop should be an object where each key is the field `name` and the value is the error message.

<CodeDemo title="Server Validation" files={inputContent.serverValidation} />

## Slots

- **base**: Input wrapper, it handles alignment, placement, and general appearance.
Expand Down Expand Up @@ -274,6 +311,30 @@ In case you need to customize the input even further, you can use the `useInput`
description: "Whether to use native HTML form validation or ARIA validation. When wrapped in a Form component, the default is `aria`. Otherwise, the default is `native`.",
default: "native"
},
{
attribute: "minLength",
type: "number",
description: "The minimum length of the text input.",
default: "-"
},
{
attribute: "maxLength",
type: "number",
description: "The maximum length of the text input.",
default: "-"
},
{
attribute: "pattern",
type: "string",
description: "A regular expression that the input value is checked against.",
default: "-"
},
{
attribute: "type",
type: "text | email | url | password | tel | search",
description: "The type of the input.",
default: "text"
},
{
attribute: "startContent",
type: "ReactNode",
Expand Down

0 comments on commit a56533c

Please sign in to comment.