Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(use-input): sync the inputValue with domRef.current.value when hovering the input #3481

Closed
wants to merge 12 commits into from
5 changes: 5 additions & 0 deletions .changeset/angry-icons-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/input": patch
---

sync the inputValue with domRef.current.value when hovering or tabbing the input (#3024, #3436)
50 changes: 49 additions & 1 deletion packages/components/input/__tests__/input.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import {render, renderHook, fireEvent} from "@testing-library/react";
import {render, renderHook, fireEvent, act} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {useForm} from "react-hook-form";

Expand Down Expand Up @@ -180,6 +180,54 @@ describe("Input", () => {

expect(inputs[1]).toBeVisible();
});

it("should sync ref.current.value with input's value after clicking the input", async () => {
jijiseong marked this conversation as resolved.
Show resolved Hide resolved
const user = userEvent.setup();
const ref = React.createRef<HTMLInputElement>();

const {container} = render(<Input ref={ref} value="value" />);

expect(ref.current).not.toBeNull();

const inputBase = container.querySelector("[data-slot='base']");

expect(inputBase).not.toBeNull();

const input = container.querySelector("input");

expect(input).not.toBeNull();

ref.current!.value = "new value";

await act(async () => {
await user.click(inputBase!);
});
expect(input).toHaveValue("new value");
});
});

it("should sync ref.current.value with input's value after hovering the input", async () => {
const user = userEvent.setup();
const ref = React.createRef<HTMLInputElement>();

const {container} = render(<Input ref={ref} value="value" />);

expect(ref.current).not.toBeNull();

const inputBase = container.querySelector("[data-slot='base']");

expect(inputBase).not.toBeNull();

const input = container.querySelector("input");

expect(input).not.toBeNull();

ref.current!.value = "new value";

await act(async () => {
await user.hover(inputBase!);
});
expect(input).toHaveValue("new value");
});

describe("Input with React Hook Form", () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/components/input/src/use-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
domRef.current?.focus();
}, [setInputValue, onClear]);

const syncRefValueToInputValue = () => {
setInputValue(domRef.current?.value);
};

// if we use `react-hook-form`, it will set the input value using the ref in register
// i.e. setting ref.current.value to something which is uncontrolled
// hence, sync the state with `ref.current.value`
Expand Down Expand Up @@ -205,7 +209,10 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
isTextInput: true,
});

const {isHovered, hoverProps} = useHover({isDisabled: !!originalProps?.isDisabled});
const {isHovered, hoverProps} = useHover({
isDisabled: !!originalProps?.isDisabled,
onHoverStart: syncRefValueToInputValue,
});

const {focusProps: clearFocusProps, isFocusVisible: isClearButtonFocusVisible} = useFocusRing();

Expand All @@ -216,6 +223,7 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
const {pressProps: clearPressProps} = usePress({
isDisabled: !!originalProps?.isDisabled,
onPress: handleClear,
onPressStart: syncRefValueToInputValue,
});

const isInvalid = validationState === "invalid" || originalProps.isInvalid || isAriaInvalid;
Expand Down