Skip to content

Commit

Permalink
fix(input-otp): filter disallowed characters from value prop (#4338)
Browse files Browse the repository at this point in the history
* fix: input should filter away disallowed characters from value prop

* chore(changeset): add space

---------

Co-authored-by: WK Wong <wingkwong.code@gmail.com>
  • Loading branch information
macci001 and wingkwong authored Dec 16, 2024
1 parent e546124 commit 2f55ecb
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-jars-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/input-otp": patch
---

Change ensures that the input value does not accept any disallowed characters when the value prop contains them (#4329)
36 changes: 35 additions & 1 deletion packages/components/input-otp/__tests__/input-otp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "@testing-library/jest-dom";

import * as React from "react";
import {render, renderHook, screen} from "@testing-library/react";
import {useForm} from "react-hook-form";
import {Controller, useForm} from "react-hook-form";
import userEvent, {UserEvent} from "@testing-library/user-event";

import {InputOtp} from "../src";
Expand Down Expand Up @@ -250,4 +250,38 @@ describe("InputOtp with react-hook-form", () => {

expect(onSubmit).toHaveBeenCalledTimes(1);
});

it("should work correctly wiht react-hook-form controller", async () => {
const {result} = renderHook(() =>
useForm({
defaultValues: {
withController: "",
},
}),
);

const {control} = result.current;

render(
<form>
<Controller
control={control}
name="withController"
render={({field}) => <InputOtp length={4} {...field} data-testid="input-otp" />}
/>
</form>,
);

const inputOtp = screen.getByTestId("input-otp");
const input = inputOtp.querySelector("input");

if (!input) {
throw new Error("Input not found");
}

await user.click(input);
await user.type(input, "1nj23aa4");

expect(input).toHaveAttribute("value", "1234");
});
});
9 changes: 9 additions & 0 deletions packages/components/input-otp/src/use-input-otp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function useInputOtp(originalProps: UseInputOtpProps) {
pasteTransformer,
containerClassName,
noScriptCSSFallback,
onChange,
...otherProps
} = props;

Expand Down Expand Up @@ -209,6 +210,14 @@ export function useInputOtp(originalProps: UseInputOtpProps) {
}),
filterDOMProps(props),
),
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target?.value;
const regex = new RegExp(allowedKeys);

if (regex.test(val)) {
onChange?.(e);
}
},
};
},
[baseDomRef, slots, baseStyles, isDisabled, isInvalid, isRequired, isReadOnly, value, length],
Expand Down
53 changes: 52 additions & 1 deletion packages/components/input-otp/stories/input-otp.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import {Meta} from "@storybook/react";
import {button, inputOtp} from "@nextui-org/theme";
import {useForm} from "react-hook-form";
import {Controller, useForm} from "react-hook-form";
import {ValidationResult} from "@react-types/shared";
import {Button} from "@nextui-org/button";
import {Form} from "@nextui-org/form";
Expand Down Expand Up @@ -177,6 +177,49 @@ const WithReactHookFormTemplate = (args) => {
);
};

const WithReactHookFormControllerTemplate = (args) => {
const {
handleSubmit,
control,
formState: {errors},
} = useForm({
defaultValues: {
otp: "",
},
});

const onSubmit = (data) => {
alert(JSON.stringify(data));
};

return (
<form className="flex flex-col gap-4 w-full max-w-[300px]" onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="otp"
render={({field}) => (
<InputOtp
{...field}
errorMessage={errors.otp && errors.otp.message}
isInvalid={!!errors.otp}
{...args}
/>
)}
rules={{
required: "OTP is required",
minLength: {
value: 4,
message: "Please enter a valid OTP",
},
}}
/>
<Button className="max-w-fit" type="submit" variant="flat">
Verify OTP
</Button>
</form>
);
};

const ServerValidationTemplate = (args) => {
const [serverErrors, setServerErrors] = React.useState({});

Expand Down Expand Up @@ -337,6 +380,14 @@ export const WithReactHookForm = {
},
};

export const WithReactHookFormController = {
render: WithReactHookFormControllerTemplate,
args: {
...defaultProps,
length: 4,
},
};

export const CustomWithClassNames = {
render: Template,
args: {
Expand Down

0 comments on commit 2f55ecb

Please sign in to comment.