Skip to content
Snippets Groups Projects
Unverified Commit fc70b70e authored by Denis Berezin's avatar Denis Berezin Committed by GitHub
Browse files

Make InputBlurChange to submit on unmount (#32102)

* Add unmount handler for InputBlurChange

* Fix e2e tests
parent 2f32c99f
No related branches found
No related tags found
No related merge requests found
Showing
with 103 additions and 27 deletions
import { ChangeEvent, useCallback, useMemo } from "react";
import { useCallback, useMemo } from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import * as MetabaseCore from "metabase/lib/core";
......@@ -89,7 +89,7 @@ const FieldHeaderSection = ({
onUpdateField,
}: FieldHeaderSectionProps) => {
const handleChangeName = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
if (event.target.value) {
onUpdateField(field, { display_name: event.target.value });
} else {
......@@ -100,7 +100,7 @@ const FieldHeaderSection = ({
);
const handleChangeDescription = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
if (event.target.value) {
onUpdateField(field, { description: event.target.value });
} else {
......
import { ChangeEvent, ReactNode, useCallback, useState } from "react";
import { ReactNode, useCallback, useState } from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import _ from "underscore";
......@@ -134,7 +134,7 @@ const TableTitleSection = ({
onChangeDescription,
}: TableTitleSectionProps) => {
const handleNameChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
if (event.target.value) {
onChangeName(event.target.value);
} else {
......@@ -145,7 +145,7 @@ const TableTitleSection = ({
);
const handleDescriptionChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
if (event.target.value) {
onChangeDescription(event.target.value);
} else {
......
import { ChangeEvent, ReactNode, useCallback } from "react";
import { ReactNode, useCallback } from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import { Link } from "react-router";
......@@ -40,7 +40,7 @@ const MetadataTableColumn = ({
onUpdateField,
}: MetadataTableColumnProps) => {
const handleChangeName = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
if (event.target.value) {
onUpdateField(field, { display_name: event.target.value });
} else {
......@@ -51,7 +51,7 @@ const MetadataTableColumn = ({
);
const handleChangeDescription = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
if (event.target.value) {
onUpdateField(field, { description: event.target.value });
} else {
......
import * as React from "react";
import cx from "classnames";
import { SettingInputBlurChange } from "./SettingInput.styled";
......@@ -35,7 +34,7 @@ const SettingInput = ({
id,
type = "text",
}: SettingInputProps) => {
const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const changeHandler = (e: { target: HTMLInputElement }) => {
const value = getValue(e.target.value, type);
onChange(value);
};
......
import { useState, useLayoutEffect, useCallback } from "react";
import * as React from "react";
import { useCallback, useLayoutEffect, useRef, useState } from "react";
import _ from "underscore";
import { useUnmount } from "react-use";
import Input, { InputProps } from "metabase/core/components/Input";
/**
......@@ -8,17 +9,16 @@ import Input, { InputProps } from "metabase/core/components/Input";
* `onBlurChange` feature, otherwise you should use <input> directly
*/
interface InputBlurChangeProps extends Omit<InputProps, "onBlur"> {
onBlurChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
export interface InputBlurChangeProps
extends Omit<InputProps, "inputRef" | "value" | "onBlur"> {
value: string | undefined;
onBlurChange?: (event: { target: HTMLInputElement }) => void;
}
const InputBlurChange = ({
value,
onChange,
onBlurChange,
...props
}: InputBlurChangeProps) => {
const InputBlurChange = (props: InputBlurChangeProps) => {
const { value, onChange, onBlurChange, ...restProps } = props;
const [internalValue, setInternalValue] = useState(value);
const inputRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => {
setInternalValue(value);
......@@ -27,6 +27,7 @@ const InputBlurChange = ({
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setInternalValue(event.target.value);
if (onChange) {
onChange(event);
}
......@@ -43,11 +44,23 @@ const InputBlurChange = ({
[value, onBlurChange],
);
const inputProps = _.omit(props, "onBlur", "onBlurChange", "onChange");
useUnmount(() => {
const lastPropsValue = value || "";
const currentValue = inputRef.current?.value || "";
if (onBlurChange && inputRef.current && lastPropsValue !== currentValue) {
onBlurChange({
target: inputRef.current,
});
}
});
const inputProps = _.omit(restProps, "onBlur", "onBlurChange", "onChange");
return (
<Input
{...inputProps}
inputRef={inputRef}
value={internalValue}
onBlur={handleBlur}
onChange={handleChange}
......
import userEvent from "@testing-library/user-event";
import { render, screen, cleanup } from "__support__/ui";
import InputBlurChange, { InputBlurChangeProps } from "./InputBlurChange";
describe("InputBlurChange", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should trigger "onBlurChange" on input blur', () => {
const {
props: { placeholder },
mocks: { onBlurChange },
} = setup();
const inputEl = screen.getByPlaceholderText(placeholder);
inputEl.focus();
inputEl.blur();
// should not be triggered if value hasn't changed
expect(onBlurChange).toHaveBeenCalledTimes(0);
userEvent.type(inputEl, "test");
inputEl.blur();
expect(onBlurChange).toHaveBeenCalledTimes(1);
expect(onBlurChange.mock.results[0].value).toBe("test");
});
it('should trigger "onBlurChange" on component unmount', () => {
const {
props: { placeholder },
mocks: { onBlurChange },
} = setup();
userEvent.type(screen.getByPlaceholderText(placeholder), "test");
cleanup();
expect(onBlurChange).toHaveBeenCalledTimes(1);
expect(onBlurChange.mock.results[0].value).toBe("test");
});
});
function setup({
value = "",
placeholder = "Type some texto",
}: Partial<InputBlurChangeProps> = {}) {
const onChange = jest.fn();
const onBlurChange = jest.fn(e => e.target.value);
render(
<InputBlurChange
value={value}
placeholder={placeholder}
onChange={onChange}
onBlurChange={onBlurChange}
/>,
);
return {
props: { value, placeholder },
mocks: { onChange, onBlurChange },
};
}
import { useCallback } from "react";
import * as React from "react";
import { t } from "ttag";
import InputBlurChange from "metabase/components/InputBlurChange";
......@@ -18,7 +17,7 @@ interface Props {
const CustomLinkText = ({ clickBehavior, updateSettings }: Props) => {
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
(e: { target: HTMLInputElement }) => {
updateSettings({
...clickBehavior,
linkTextTemplate: e.target.value,
......
import { ChangeEvent, useCallback } from "react";
import { useCallback } from "react";
import { t } from "ttag";
import InputBlurChange from "metabase/components/InputBlurChange";
import Radio from "metabase/core/components/Radio";
......@@ -47,7 +47,7 @@ const ParameterSettings = ({
onRemoveParameter,
}: ParameterSettingsProps): JSX.Element => {
const handleNameChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: { target: HTMLInputElement }) => {
onChangeName(event.target.value);
},
[onChangeName],
......
......@@ -66,7 +66,7 @@ const SpecificDatePicker = ({
value={date ? date.format(dateFormat) : ""}
autoFocus={autoFocus}
onFocus={onFocus}
onBlurChange={({ target: { value } }: any) => {
onBlurChange={({ target: { value } }) => {
const date = moment(value, dateFormat);
if (date.isValid()) {
handleChange(date, hours, minutes);
......
......@@ -50,7 +50,7 @@ const ChartNestedSettingsSeriesSingle = ({
subtitle={
seriesCardName === computedSettings.title ? "" : seriesCardName
}
onBlurChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onBlurChange={e =>
onChangeObjectSettings(object, { title: e.target.value })
}
/>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment