Merge pull request #4112 from urbit/lf/async-double-submit

AsyncButton: protect against double submit
This commit is contained in:
matildepark 2021-01-21 14:18:52 -05:00 committed by GitHub
commit 2f3e7028cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 92 additions and 33 deletions

3
package-lock.json generated Normal file
View File

@ -0,0 +1,3 @@
{
"lockfileVersion": 1
}

View File

@ -1698,9 +1698,9 @@
"integrity": "sha512-kBzJueOoGDVF2knGt+Kf5ylvil6+V1qn8/RqAj1S6wUTnfUfAMRzDp4LQI2MxLI8Is0OG3XCErVSOUImU6R3lg=="
},
"@tlon/indigo-react": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.16.tgz",
"integrity": "sha512-9bQ43cXiJGOsrihwy8+MBfG4WroKucZJOm4whfSjsNFCHorjS+5Y/6nWl2hEwHo068XONFmD7xlDE1QBMTk+pA==",
"version": "1.2.17",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.17.tgz",
"integrity": "sha512-D53HDLbqkRX3nY5zcXv8DRHw7FhsCGYfY3xa8CbaFfhFupdXBHi96UURi9Qq3sBc4FHgnPj45eJflji7Yj3gYg==",
"requires": {
"@reach/menu-button": "^0.10.5",
"react": "^16.13.1",
@ -10138,7 +10138,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -10159,12 +10160,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -10179,17 +10182,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -10306,7 +10312,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -10318,6 +10325,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -10332,6 +10340,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -10339,12 +10348,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -10363,6 +10374,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -10424,7 +10436,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
@ -10452,7 +10465,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -10464,6 +10478,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -10541,7 +10556,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -10577,6 +10593,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -10596,6 +10613,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -10639,12 +10657,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -11125,7 +11145,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -11146,12 +11167,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -11166,17 +11189,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -11293,7 +11319,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -11305,6 +11332,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -11319,6 +11347,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -11326,12 +11355,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -11350,6 +11381,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -11411,7 +11443,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
@ -11439,7 +11472,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -11451,6 +11485,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -11528,7 +11563,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -11564,6 +11600,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -11583,6 +11620,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -11626,12 +11664,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},

View File

@ -10,7 +10,7 @@
"@reach/tabs": "^0.10.5",
"@tlon/indigo-dark": "^1.0.6",
"@tlon/indigo-light": "^1.0.6",
"@tlon/indigo-react": "1.2.16",
"@tlon/indigo-react": "1.2.17",
"@tlon/sigil-js": "^1.4.3",
"aws-sdk": "^2.726.0",
"big-integer": "^1.6.48",

View File

@ -29,10 +29,15 @@ export function AsyncButton({
}, [status]);
return (
<Button disabled={!isValid} type="submit" {...rest}>
<Button
hideDisabled={isSubmitting}
disabled={!isValid || isSubmitting}
type="submit"
{...rest}
>
{isSubmitting ? (
<LoadingSpinner
foreground={rest.primary ? "white" : 'black'}
foreground={rest.primary ? "white" : "black"}
background="gray"
/>
) : success === true ? (

View File

@ -8,6 +8,7 @@ import { useFormikContext } from "formik";
interface AsyncActionProps {
children: ReactNode;
name: string;
disabled?: boolean;
onClick: (e: React.MouseEvent) => Promise<void>;
}
@ -15,6 +16,7 @@ export function StatelessAsyncAction({
children,
onClick,
name = '',
disabled = false,
...rest
}: AsyncActionProps & Parameters<typeof Action>[0]) {
const {
@ -23,7 +25,10 @@ export function StatelessAsyncAction({
} = useStatelessAsyncClickable(onClick, name);
return (
<Action onClick={handleClick} {...rest}>
<Action
hideDisabled={!disabled}
disabled={disabled || state === 'loading'}
onClick={handleClick} {...rest}>
{state === "error" ? (
"Error"
) : state === "loading" ? (

View File

@ -7,7 +7,7 @@ import { useStatelessAsyncClickable } from "~/logic/lib/useStatelessAsyncClickab
interface AsyncButtonProps {
children: ReactNode;
name: string;
name?: string;
onClick: (e: React.MouseEvent) => Promise<void>;
}
@ -15,6 +15,7 @@ export function StatelessAsyncButton({
children,
onClick,
name = "",
disabled = false,
...rest
}: AsyncButtonProps & Parameters<typeof Button>[0]) {
const {
@ -23,7 +24,12 @@ export function StatelessAsyncButton({
} = useStatelessAsyncClickable(onClick, name);
return (
<Button onClick={handleClick} {...rest}>
<Button
hideDisabled={!disabled}
disabled={disabled || state === 'loading'}
onClick={handleClick}
{...rest}
>
{state === "error" ? (
"Error"
) : state === "loading" ? (