add react hooks eslint plugin (#3059)

* add eslint react hooks

* fix code breaking the rules-of-hooks eslint rule

* fix spinnerWhileRendering for tests

Co-authored-by: Wyatt Johnson <wyattjoh@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Tessa Thornton
2020-08-05 14:42:50 +00:00
committed by GitHub
co-authored by Wyatt Johnson kodiakhq[bot]
parent 9d02391062
commit 0f1dd7d707
16 changed files with 152 additions and 117 deletions
@@ -21,10 +21,6 @@ interface Props {
}
const UserStatusDetailsContainer: FunctionComponent<Props> = ({ user }) => {
if (!user.status.ban.active && !user.status.suspension.active) {
return null;
}
const activeBan = useMemo(() => {
return user.status.ban.history.find((item) => item.active);
}, [user]);
@@ -41,6 +37,10 @@ const UserStatusDetailsContainer: FunctionComponent<Props> = ({ user }) => {
minute: "2-digit",
});
if (!user.status.ban.active && !user.status.suspension.active) {
return null;
}
return (
<div>
<Popover
@@ -17,20 +17,23 @@ interface Props {
}
const AddSiteRoute: FunctionComponent<Props> = ({ data }) => {
const { setMessage, clearMessage } = useNotification();
const onSiteEdit = useCallback(
(name: string) => {
setMessage(
<Localized id="configure-sites-edit-success" $site={name}>
<AppNotification icon="check_circle_outline" onClose={clearMessage}>
Changes to {name} have been saved
</AppNotification>
</Localized>
);
},
[setMessage, clearMessage]
);
if (!data || !data.site) {
return null;
}
const { site } = data;
const { setMessage, clearMessage } = useNotification();
const onSiteEdit = useCallback((name: string) => {
setMessage(
<Localized id="configure-sites-edit-success" $site={name}>
<AppNotification icon="check_circle_outline" onClose={clearMessage}>
Changes to {name} have been saved
</AppNotification>
</Localized>
);
}, []);
return (
<ConfigBox
title={
@@ -38,13 +38,6 @@ interface Props {
site?: Site | null;
}
const DashboardContainer: React.FunctionComponent<Props> = (props) => {
if (!props.site) {
return null;
}
const sites = props.query
? props.query.sites.edges.map((edge) => edge.node)
: [];
const [lastUpdated, setLastUpdated] = useState<string>(new Date().toString());
const [loadMore, isLoadingMore] = useLoadMore(props.relay, 10);
const [, isRefetching] = useRefetch<
@@ -53,6 +46,12 @@ const DashboardContainer: React.FunctionComponent<Props> = (props) => {
const onRefetch = useCallback(() => {
setLastUpdated(new Date().toString());
}, []);
if (!props.site) {
return null;
}
const sites = props.query
? props.query.sites.edges.map((edge) => edge.node)
: [];
return (
<MainLayout className={styles.root}>
<Popover
@@ -23,7 +23,7 @@ function calculateBottomPadding(width: number, height: number) {
return `${(height / width) * 100}%`;
}
const oEmbed: FunctionComponent<Props> = ({
const OEmbed: FunctionComponent<Props> = ({
url,
type,
className,
@@ -47,7 +47,7 @@ const oEmbed: FunctionComponent<Props> = ({
if (containerRef.current) {
setMaxWidth(containerRef.current.offsetWidth);
}
}, [containerRef.current, maxWidth]);
}, [containerRef, maxWidth]);
const onLoad = useCallback(() => {
if (width && height && containerRef && containerRef.current) {
@@ -96,7 +96,7 @@ const oEmbed: FunctionComponent<Props> = ({
);
}
}, 100);
}, [iframeRef, iframeRef.current, containerRef.current, width, height]);
}, [iframeRef, containerRef, width, height]);
return (
<div className={styles.container} ref={containerRef}>
@@ -117,4 +117,4 @@ const oEmbed: FunctionComponent<Props> = ({
);
};
export default oEmbed;
export default OEmbed;
@@ -19,13 +19,12 @@ const ReportFlowContainer: FunctionComponent<Props> = ({
comment,
onClose,
}) => {
if (!viewer) {
return null;
}
const onFormClose = useCallback(() => {
onClose();
}, [onClose]);
if (!viewer) {
return null;
}
return <ReportCommentFormContainer comment={comment} onClose={onFormClose} />;
};
@@ -33,20 +33,6 @@ const IgnoredTombstoneOrHideContainer: FunctionComponent<Props> = ({
}) => {
const deleted = Boolean(!comment.author);
if (deleted) {
return (
<>
<Tombstone className={CLASSES.deletedTombstone} fullWidth>
<Localized id="comments-tombstone-deleted">
This comment is no longer available. The commenter has deleted their
account.
</Localized>
</Tombstone>
{children}
</>
);
}
const ignored = Boolean(
comment.author &&
viewer &&
@@ -66,11 +52,30 @@ const IgnoredTombstoneOrHideContainer: FunctionComponent<Props> = ({
if (!tombstone && ignored === true && previouslyIgnored === false) {
setTombstone(true);
}
}, [ignored, previouslyIgnored, tombstone, setTombstone]);
}, [
ignored,
previouslyIgnored,
tombstone,
setTombstone,
singleConversationView,
]);
const onShowComment = useCallback(() => {
setForceVisible(true);
}, [setForceVisible]);
if (deleted) {
return (
<>
<Tombstone className={CLASSES.deletedTombstone} fullWidth>
<Localized id="comments-tombstone-deleted">
This comment is no longer available. The commenter has deleted their
account.
</Localized>
</Tombstone>
{children}
</>
);
}
if (!ignored || forceVisible) {
return <>{children}</>;
@@ -39,15 +39,15 @@ function callWhenReallyIdle(callback: () => void) {
* Show spinner, wait for browser to idle and start rendering.
*/
const SpinnerWhileRendering: FunctionComponent<Props> = (props) => {
// In our tests, we don't actually "render", so just skip this.
if (process.env.NODE_ENV === "test") {
return <>{props.children}</>;
}
const [hidden, setHidden] = useState(true);
useEffect(() => {
// Ensure window has bee
return callWhenReallyIdle(() => setHidden(false));
}, [setHidden]);
// In our tests, we don't actually "render", so just skip this.
if (process.env.NODE_ENV === "test") {
return <>{props.children}</>;
}
return (
<>
{hidden && (
@@ -0,0 +1,16 @@
import React, { FunctionComponent } from "react";
import SpinnerWhileRendering from "./SpinnerWhileRendering";
interface Props {
children: React.ReactNode;
}
const SpinnerWhileRenderingWrapper: FunctionComponent<Props> = (props) => {
if (process.env.NODE_ENV === "test") {
return <>{props.children}</>;
}
return <SpinnerWhileRendering {...props} />;
};
export default SpinnerWhileRenderingWrapper;
@@ -0,0 +1,4 @@
export {
default,
default as SpinnerWhileRendering,
} from "./SpinnerWhileRenderingWrapper";
@@ -40,14 +40,14 @@ function callWhenReallyIdle(callback: () => void) {
*/
const SpinnerWhileRendering: FunctionComponent<Props> = (props) => {
// In our tests, we don't actually "render", so just skip this.
if (process.env.NODE_ENV === "test") {
return <>{props.children}</>;
}
const [hidden, setHidden] = useState(true);
useEffect(() => {
// Ensure window has bee
return callWhenReallyIdle(() => setHidden(false));
}, [setHidden]);
if (process.env.NODE_ENV === "test") {
return <>{props.children}</>;
}
return (
<>
{hidden && (
@@ -81,7 +81,7 @@ interface FormProps {
password: string;
}
const changeEmailContainer: FunctionComponent<Props> = ({
const ChangeEmailContainer: FunctionComponent<Props> = ({
viewer,
settings,
}) => {
@@ -508,6 +508,6 @@ const enhanced = withFragmentContainer<Props>({
}
}
`,
})(changeEmailContainer);
})(ChangeEmailContainer);
export default enhanced;
@@ -31,7 +31,7 @@
display: flex;
flex-basis: calc(var(--spacing-2) + 14px);
justify-content: center;
align-items: start;
align-items: flex-start;
}
.title {
@@ -46,10 +46,6 @@ const CallOut: FunctionComponent<Props> = ({
visible = true,
onClose,
}) => {
if (!visible) {
return null;
}
const rootClasses = cn(
classes.root,
{
@@ -86,6 +82,9 @@ const CallOut: FunctionComponent<Props> = ({
onClose();
}, [onClose]);
if (!visible) {
return null;
}
return (
<div className={rootClasses}>