Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing React import

The component uses React.useState but doesn't import React.

+import * as React from "react";
 import {Button, Form, Input} from "@nextui-org/react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {Button, Form, Input} from "@nextui-org/react";
import * as React from "react";
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);

Comment on lines +1 to +5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing React import.

The component uses React.useState but React is not imported.

+import React from "react";
 import {Button, Form, Input} from "@nextui-org/react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {Button, Form, Input} from "@nextui-org/react";
export default function App() {
const [submitted, setSubmitted] = React.useState(null);
import React from "react";
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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing React import

The component uses React.useState but doesn't import React.

+import * as React from "react";
 import {Button, Form, Input} from "@nextui-org/react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {Button, Form, Input} from "@nextui-org/react";
import * as React from "react";
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);
};
Comment on lines +7 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and type safety

As a documentation example, it should demonstrate proper error handling and type safety.

-  const onSubmit = async (e) => {
+  type FormData = {
+    username: string;
+  };
+
+  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
     setIsLoading(true);
+    try {
+      const formData = new FormData(e.currentTarget);
+      const data: FormData = {
+        username: formData.get('username') as string
+      };
+      const result = await callServer(data);
-    const data = Object.fromEntries(new FormData(e.currentTarget));
-    const result = await callServer(data);
-
-    setErrors(result.errors);
+      setErrors(result.errors);
+    } catch (error) {
+      setErrors({ username: "An error occurred. Please try again." });
+    }
     setIsLoading(false);
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
};
type FormData = {
username: string;
};
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
try {
const formData = new FormData(e.currentTarget);
const data: FormData = {
username: formData.get('username') as string
};
const result = await callServer(data);
setErrors(result.errors);
} catch (error) {
setErrors({ username: "An error occurred. Please try again." });
}
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
6 changes: 3 additions & 3 deletions apps/docs/content/docs/guide/forms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Most fields should have a visible label. In rare exceptions, the `aria-label` or

## Submitting data

How you submit form data depends on your framework, application, and server. By default,**HTML** forms are submitted via a full-page refresh in the browser.
How you submit form data depends on your framework, application, and server. By default, **HTML** forms are submitted via a full-page refresh in the browser.
You can call `preventDefault` in the `onSubmit` event to handle form data submission via an API.

Frameworks like [Next.js](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms), [Remix](https://remix.run/docs/en/main/guides/forms), and [React Router](https://reactrouter.com/en/main/route/form-submission) provide their own ways to handle form submission.
Expand Down Expand Up @@ -196,7 +196,7 @@ NextUI supports native HTML constraint validation and allows for custom validati
#### Built-in validation

NextUI form components support [native HTML validation](https://developer.mozilla.org/docs/Web/HTML/Constraint_validation) attributes like `isRequired` and `minLength`.
These constraints are checked by the browser when the user commits changes (e.g., onBlur) or submits the form.
These constraints are checked by the browser when the user commits changes (e.g., on blur) or submits the form.
You can display validation errors with custom styles instead of the browser's default UI.

```tsx {10}
Expand Down Expand Up @@ -235,7 +235,7 @@ Supported constraints include:
- `minValue` and `maxValue` specify the minimum and maximum value in a date picker or number field.
- `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 builtin validation for email addresses and URLs.
- `type="email"` and `type="url"` provide built-in validation for email addresses and URLs.

See each component's documentation for more details on the supported validation props.

Expand Down