Skip to content

Commit

Permalink
Fixes custom error rendering in TextField (#16487)
Browse files Browse the repository at this point in the history
#### Pull request checklist

- [x] Addresses an existing issue: Fixes #16049 
- [x] Include a change request file using `$ yarn change`

#### Description of changes

TextField renders all the error messages under a p tag with custom styles, that's fine as long as the error message is string.
When there is a custom error message (i.e., JSX.Element) rendering it under the p tag violates DOM validation (e.g, div can't be child of p). This PR addresses this issue.
  • Loading branch information
hatpick authored Jan 15, 2021
1 parent 678b08a commit 0229138
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Fixes TextField custom error message rendering",
"packageName": "@fluentui/react-internal",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2021-01-15T00:34:06.681Z"
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as React from 'react';
import { TextField } from '@fluentui/react/lib/TextField';
import { Stack, IStackTokens } from '@fluentui/react/lib/Stack';
import { Stack, IStackTokens, IStackStyles } from '@fluentui/react/lib/Stack';
import { Toggle } from '@fluentui/react/lib/Toggle';
import { useBoolean } from '@fluentui/react-hooks';
import { Icon, IIconStyles } from '@fluentui/react/lib/Icon';
import { Text } from '@fluentui/react/lib/Text';

const stackTokens: IStackTokens = {
childrenGap: 20,
maxWidth: 350,
};

const richErrorIconStyles: Partial<IIconStyles> = { root: { color: 'red' } };
const richErrorStackStyles: Partial<IStackStyles> = { root: { height: 24 } };
const richErrorStackTokens: IStackTokens = { childrenGap: 8 };

const getErrorMessage = (value: string): string => {
return value.length < 3 ? '' : `Input value length must be less than 3. Actual length is ${value.length}.`;
};
Expand All @@ -19,6 +25,17 @@ const getErrorMessagePromise = (value: string): Promise<string> => {
});
};

const getRichErrorMessage = (value: string) => {
return value.length < 3 ? (
''
) : (
<Stack styles={richErrorStackStyles} verticalAlign="center" horizontal tokens={richErrorStackTokens}>
<Icon iconName="Error" styles={richErrorIconStyles} />
<Text variant="smallPlus">Input value length must be less than 3. Actual length is {value.length}.</Text>
</Stack>
);
};

export const TextFieldErrorMessageExample: React.FunctionComponent = () => {
const [showFields, { toggle: toggleShowFields }] = useBoolean(false);

Expand Down Expand Up @@ -82,6 +99,11 @@ export const TextFieldErrorMessageExample: React.FunctionComponent = () => {
placeholder="This field always has an error"
errorMessage="This is a statically set error message"
/>
<TextField
label="Custom rich error message"
defaultValue="This value is too long"
onGetErrorMessage={getRichErrorMessage}
/>
</>
)}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,7 @@ export class TextFieldBase extends React.Component<ITextFieldProps, ITextFieldSt
{onRenderDescription(this.props, this._onRenderDescription)}
{errorMessage && (
<div role="alert">
<DelayedRender>
<p className={classNames.errorMessage}>
<span data-automation-id="error-message">{errorMessage}</span>
</p>
</DelayedRender>
<DelayedRender>{this._renderErrorMessage()}</DelayedRender>
</div>
)}
</span>
Expand Down Expand Up @@ -446,6 +442,28 @@ export class TextFieldBase extends React.Component<ITextFieldProps, ITextFieldSt
return errorMessage || '';
}

/**
* Renders error message based on the type of the message.
*
* - If error message is string, it will render using the built in styles.
* - If error message is an element, user has full control over how it's rendered.
*/
private _renderErrorMessage(): JSX.Element | null {
const errorMessage = this._errorMessage;

return errorMessage ? (
typeof errorMessage === 'string' ? (
<p className={this._classNames.errorMessage}>
<span data-automation-id="error-message">{errorMessage}</span>
</p>
) : (
<div className={this._classNames.errorMessage} data-automation-id="error-message">
{errorMessage}
</div>
)
) : null;
}

/**
* If a custom description render function is supplied then treat description as always available.
* Otherwise defer to the presence of description or error message text.
Expand Down

0 comments on commit 0229138

Please sign in to comment.