From e887df52d5a7de2e65aaa481f675604bf4c89d8a Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 5 Jan 2024 10:03:31 -0800 Subject: [PATCH] Fix clear logic to work in both controlled and uncontrolled + tests - significantly easier to test in Cypress/E2E than Jest/jsdom + bonus perf memoization --- .../form/text_area/text_area.spec.tsx | 48 +++++++++++++++++++ src/components/form/text_area/text_area.tsx | 46 ++++++++++++------ 2 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 src/components/form/text_area/text_area.spec.tsx diff --git a/src/components/form/text_area/text_area.spec.tsx b/src/components/form/text_area/text_area.spec.tsx new file mode 100644 index 00000000000..0170b856709 --- /dev/null +++ b/src/components/form/text_area/text_area.spec.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/// +/// +/// + +import React, { useState } from 'react'; +import { EuiTextArea } from './text_area'; + +describe('EuiTextArea', () => { + describe('isClearable', () => { + it('works for uncontrolled components', () => { + cy.realMount(); + + cy.get('textarea').type('hello world'); + cy.get('textarea').should('have.value', 'hello world'); + + cy.get('[data-test-subj="clearTextAreaButton"]').click(); + cy.get('textarea').should('have.value', ''); + }); + + it('works for controlled components', () => { + const ControlledTextArea = ({}) => { + const [value, setValue] = useState(''); + return ( + setValue(e.target.value)} + isClearable + /> + ); + }; + cy.realMount(); + + cy.get('textarea').type('hello world'); + cy.get('textarea').should('have.value', 'hello world'); + + cy.get('[data-test-subj="clearTextAreaButton"]').click(); + cy.get('textarea').should('have.value', ''); + }); + }); +}); diff --git a/src/components/form/text_area/text_area.tsx b/src/components/form/text_area/text_area.tsx index 80795f71438..d3253dc1742 100644 --- a/src/components/form/text_area/text_area.tsx +++ b/src/components/form/text_area/text_area.tsx @@ -6,10 +6,18 @@ * Side Public License, v 1. */ -import React, { TextareaHTMLAttributes, Ref, FunctionComponent } from 'react'; -import { CommonProps } from '../../common'; +import React, { + TextareaHTMLAttributes, + Ref, + FunctionComponent, + useRef, + useMemo, +} from 'react'; import classNames from 'classnames'; +import { CommonProps } from '../../common'; +import { useCombinedRefs } from '../../../services'; + import { EuiFormControlLayout } from '../form_control_layout'; import { EuiValidatableControl } from '../validatable_control'; import { useFormContext } from '../eui_form_context'; @@ -90,24 +98,34 @@ export const EuiTextArea: FunctionComponent = (props) => { definedRows = 6; } - const onClear = () => { - if (rest.onChange) { - rest.onChange({ - target: { value: '' }, - } as React.ChangeEvent); + const ref = useRef(null); + const refs = useCombinedRefs([ref, inputRef]); + + const clear = useMemo(() => { + if (isClearable) { + return { + onClick: () => { + if (ref.current) { + ref.current.value = ''; + const event = new Event('input', { + bubbles: true, + cancelable: false, + }); + ref.current.dispatchEvent(event); + ref.current.focus(); // set focus back to the textarea + } + }, + 'data-test-subj': 'clearTextAreaButton', + }; } - }; + }, [isClearable]); return ( @@ -118,7 +136,7 @@ export const EuiTextArea: FunctionComponent = (props) => { rows={definedRows} name={name} id={id} - ref={inputRef} + ref={refs} placeholder={placeholder} > {children}