Skip to content

Commit

Permalink
Fix clear logic to work in both controlled and uncontrolled
Browse files Browse the repository at this point in the history
+ tests - significantly easier to test in Cypress/E2E than Jest/jsdom

+ bonus perf memoization
  • Loading branch information
cee-chen committed Jan 5, 2024
1 parent 6b869c2 commit e887df5
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 14 deletions.
48 changes: 48 additions & 0 deletions src/components/form/text_area/text_area.spec.tsx
Original file line number Diff line number Diff line change
@@ -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.
*/

/// <reference types="cypress" />
/// <reference types="cypress-real-events" />
/// <reference types="../../../../cypress/support" />

import React, { useState } from 'react';
import { EuiTextArea } from './text_area';

describe('EuiTextArea', () => {
describe('isClearable', () => {
it('works for uncontrolled components', () => {
cy.realMount(<EuiTextArea isClearable />);

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 (
<EuiTextArea
value={value}
onChange={(e) => setValue(e.target.value)}
isClearable
/>
);
};
cy.realMount(<ControlledTextArea />);

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', '');
});
});
});
46 changes: 32 additions & 14 deletions src/components/form/text_area/text_area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -90,24 +98,34 @@ export const EuiTextArea: FunctionComponent<EuiTextAreaProps> = (props) => {
definedRows = 6;
}

const onClear = () => {
if (rest.onChange) {
rest.onChange({
target: { value: '' },
} as React.ChangeEvent<HTMLTextAreaElement>);
const ref = useRef<HTMLTextAreaElement | null>(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 (
<EuiFormControlLayout
fullWidth={fullWidth}
isLoading={isLoading}
isInvalid={isInvalid}
clear={
isClearable
? { onClick: onClear, 'data-test-subj': 'clearTextAreaButton' }
: undefined
}
clear={clear}
icon={icon}
className="euiFormControlLayout--euiTextArea"
>
Expand All @@ -118,7 +136,7 @@ export const EuiTextArea: FunctionComponent<EuiTextAreaProps> = (props) => {
rows={definedRows}
name={name}
id={id}
ref={inputRef}
ref={refs}
placeholder={placeholder}
>
{children}
Expand Down

0 comments on commit e887df5

Please sign in to comment.