Skip to content

Instantly share code, notes, and snippets.

@tkrotoff
Last active June 18, 2023 10:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tkrotoff/885d0440fd46ea4d3d4919b3914a5223 to your computer and use it in GitHub Desktop.
Save tkrotoff/885d0440fd46ea4d3d4919b3914a5223 to your computer and use it in GitHub Desktop.
I want an error tooltip on a button when clicked, let's use the browser native input validation error message
import { forwardRef } from 'react';
// ref is optional with forwardRef(), I want it to be mandatory
// type ... = ReturnType<typeof forwardRef<T, P>>;
type ForwardMandatoryRefComponent<T, P> = React.ForwardRefExoticComponent<
React.PropsWithoutRef<P> & React.RefAttributes<T> & { ref: React.Ref<T> }
>;
type Props = {
// Why? https://stackoverflow.com/a/25367640
parentBorderWidth: number;
};
/**
* What a nice hack!
* I want an error tooltip on a button when clicked,
* let's use the browser native input validation error message instead of re-inventing my own.
*
* Parent should have "position: relative", no need for a form element.
*
* Example:
*
* ```
* <Link
* href={...}
* onClick={e => {
* e.preventDefault();
*
* const inputTooltip = inputTooltipRef.current!;
* inputTooltip.setCustomValidity('Message to display inside the browser native tooltip');
* inputTooltip.reportValidity();
* }}
* className="position-relative btn btn-primary"
* >
* My link text
* <FormValidationErrorMessageHack ref={inputTooltipRef} parentBorderWidth={1} />
* </Link>
* ```
*
* FIXME [Firefox Mobile Android does not show form validation errors](https://github.com/mozilla-mobile/fenix/issues/27986)
*
* https://twitter.com/tkrotoff/status/1670378044471533571
* https://gist.github.com/tkrotoff/885d0440fd46ea4d3d4919b3914a5223
*/
export const FormValidationErrorMessageHack: ForwardMandatoryRefComponent<HTMLInputElement, Props> =
forwardRef<HTMLInputElement, Props>(function FormValidationErrorMessageHack(
{ parentBorderWidth },
ref
) {
return (
<>
{/* Avoid Lighthouse accessibility issue "Form elements do not have associated labels" */}
<label htmlFor="FormValidationErrorMessageHack" style={{ visibility: 'hidden', height: 0 }}>
{/* https://www.fileformat.info/info/unicode/char/200b/index.htm */}
&#8203;
</label>
<input
id="FormValidationErrorMessageHack"
ref={ref}
aria-hidden="true"
// No virtual keyboard
inputMode="none"
// Prevent the input to gain focus
tabIndex={-1}
style={{
position: 'absolute',
// Center the input over parent
left: -parentBorderWidth,
top: -parentBorderWidth,
width: `calc(100% + ${parentBorderWidth}px * 2)`,
height: `calc(100% + ${parentBorderWidth}px * 2)`,
// Not working with Firefox 111: tooltip is not displayed
//visibility: 'hidden',
opacity: 0,
pointerEvents: 'none'
}}
/>
</>
);
});
import { render, screen } from '@testing-library/react';
import { useRef } from 'react';
import { FormValidationErrorMessageHack } from './FormValidationErrorMessageHack';
test('display an HTML error message', () => {
function ButtonWithTooltip() {
const inputTooltipRef = useRef<HTMLInputElement>(null);
return (
<button
type="button"
onClick={e => {
e.preventDefault();
const inputTooltip = inputTooltipRef.current!;
inputTooltip.setCustomValidity('My tooltip message');
inputTooltip.reportValidity();
}}
style={{ position: 'relative' }}
>
My button text
<FormValidationErrorMessageHack ref={inputTooltipRef} parentBorderWidth={1} />
</button>
);
}
render(<ButtonWithTooltip />);
expect(screen.queryByText('My tooltip message')).toBeNull();
const inputValidation = screen.getByLabelText<HTMLInputElement>('\u200B');
expect(inputValidation.validity.valid).toEqual(true);
expect(inputValidation.validationMessage).toEqual('');
const button = screen.getByRole<HTMLButtonElement>('button', { name: 'My button text' });
button.click();
// FIXME Does not work with jsdom & Happy DOM
//screen.getByText('My tooltip message');
expect(inputValidation.validity.valid).toEqual(false);
expect(inputValidation.validationMessage).toEqual('My tooltip message');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment