Webclient: Handle firing an event once (#4499)

* draft: handle firing an event once

* lint

* Prevent rapid double-click on sending messages

* no rest spread on single primative when sibling components exist

* clear message instead of using a fireOnce handler.

* fix tests

* remove unnecessary validate mock
This commit is contained in:
Brent Clark 2022-01-30 11:14:28 -06:00 committed by GitHub
parent 4bb13677c8
commit 513fcb0908
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 21467 additions and 161 deletions

View file

@ -0,0 +1 @@
export * from './useFireOnce'

View file

@ -0,0 +1,103 @@
import {
render,
fireEvent,
getByRole,
waitFor,
act
} from '@testing-library/react';
import { useFireOnce } from './useFireOnce';
describe('useFireOnce hook', () => {
test('it only fires once when button is clicked twice', async () => {
// Mock a promise with a delay
const onClickWithPromise = jest.fn((e) => {
e.preventDefault()
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 100);
});
});
function Button(props) {
const { children, onClick } = props
const [buttonIsDisabled, setButtonIsDisabled, handleClickOnce] = useFireOnce(onClick)
return <button onClick={handleClickOnce} disabled={buttonIsDisabled}>{children}</button>
}
// render the button
const { getByRole } = render(
<Button onClick={onClickWithPromise}>Click Me!</Button>
);
//Grab the button from the DOM and confirm it initialized in an enabled state
const button = getByRole('button', { name: 'Click Me!' });
expect(button).toBeEnabled();
// Simulate two click events in a row
fireEvent.click(button);
fireEvent.click(button);
// Confirm that it's disabled
await waitFor(() => {
expect(button).toBeDisabled();
});
// Confirm it became enabled after the timeout and that the click event was only fired once
await waitFor(
() => {
expect(onClickWithPromise).toHaveBeenCalledTimes(1);
},
{ timeout: 100 }
);
});
test('it only fires once when form is submitted twice', async () => {
// Mock a promise with a delay
const onClickWithPromise = jest.fn((e) => {
e.preventDefault()
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 100);
});
});
function Form(props) {
const { onSubmit } = props
const [buttonIsDisabled, setButtonIsDisabled, handleSubmitOnce] = useFireOnce(onSubmit)
return (
<form onSubmit={handleSubmitOnce}>
<input type="text" defaultValue="Hell World" name="thing-to-say" />
<button disabled={buttonIsDisabled}>Click Me!</button>
</form>
)
}
// render the form
const { getByRole } = render(
<Form onSubmit={onClickWithPromise} />
);
//Grab the button from the DOM and confirm it initialized in an enabled state
const button = getByRole('button', { name: 'Click Me!' });
expect(button).toBeEnabled();
// Simulate two click events in a row
fireEvent.click(button);
fireEvent.click(button);
// Confirm that it's disabled
await waitFor(() => {
expect(button).toBeDisabled();
});
// Confirm it became enabled after the timeout and that the click event was only fired once
await waitFor(
() => {
expect(onClickWithPromise).toHaveBeenCalledTimes(1);
},
{ timeout: 100 }
);
});
});

View file

@ -0,0 +1,17 @@
import { useCallback, useState } from 'react';
import { useReduxEffect } from 'hooks';
import { ServerTypes } from 'store';
type UseFireOnceType = (...args: any) => any;
export function useFireOnce<T extends UseFireOnceType>(fn: T): [boolean, any, any] {
const [actionIsInFlight, setActionIsInFlight] = useState(false)
const handleFireOnce = useCallback((args) => {
setActionIsInFlight(true);
fn(args);
}, [])
function resetInFlightStatus() {
setActionIsInFlight(false);
}
return [actionIsInFlight, resetInFlightStatus, handleFireOnce]
}