Skip to content

Commit

Permalink
Add react-hooks/exhaustive-deps ESLint rule (#6180)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Jul 26, 2021
1 parent cf61575 commit a11e54d
Show file tree
Hide file tree
Showing 22 changed files with 79 additions and 59 deletions.
10 changes: 10 additions & 0 deletions .changeset/nine-eagles-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@keystone-ui/tooltip': patch
'@keystone-next/auth': patch
'@keystone-next/cloudinary': patch
'@keystone-next/fields': patch
'@keystone-next/fields-document': patch
'@keystone-next/keystone': patch
---

Fixed issues with React hooks dependency arrays
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module.exports = {
'no-undef': 'error',
'no-unused-expressions': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
Expand Down
6 changes: 3 additions & 3 deletions design-system/packages/tooltip/src/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const Tooltip = ({
});

const tooltipId = useId();
const showTooltip = useCallback(() => setOpen(true), []);
const hideTooltip = useCallback(() => setOpen(false), []);
const showTooltip = useCallback(() => setOpen(true), [setOpen]);
const hideTooltip = useCallback(() => setOpen(false), [setOpen]);
const internalRef = useRef<HTMLElement>(null);

// avoid overriding the consumer's `onClick` handler
Expand All @@ -77,7 +77,7 @@ export const Tooltip = ({

return () => triggerEl.removeEventListener('click', hideTooltip);
}
}, [isOpen]);
}, [isOpen, hideOnClick, hideTooltip]);

return (
<Fragment>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/MobileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function MobileMenu({ handleClose }: MobileMenuProps) {
return () => {
document.body.removeEventListener('keydown', handleEsc);
};
}, []);
}, [mobileNavIsOpen, handleClose]);

return (
<Fragment>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/Theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function Theme() {
if (detectedTheme !== 'light') {
setTheme(detectedTheme);
}
});
}, []);

return (
<Global
Expand Down
4 changes: 2 additions & 2 deletions docs/components/docs/DocumentEditorDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,15 @@ export const DocumentEditorDemo = () => {
const isShiftPressedRef = useKeyDownRef('Shift');
const editor = useMemo(
() => createDocumentEditor(documentFeatures, componentBlocks, emptyObj, isShiftPressedRef),
[documentFeatures]
[documentFeatures, isShiftPressedRef]
);

// this is why we're creating the editor ourselves and not using the DocumentEditor component
useEffect(() => {
// we want to force normalize when the document features change so
// that no invalid things exist after a user changes something
Editor.normalize(editor, { force: true });
}, [documentFeatures]);
}, [editor, documentFeatures, isShiftPressedRef]);

return (
<div
Expand Down
4 changes: 2 additions & 2 deletions docs/components/docs/TableOfContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export function TableOfContents({
container: React.RefObject<HTMLElement | null>;
headings: Heading[];
}) {
let allIds = headings.map(h => h.id);
let [visibleIds, setVisibleIds] = useState<Array<string | null>>([]);
let [lastVisibleId, setLastVisbleId] = useState<string | null>(null);

Expand All @@ -40,6 +39,7 @@ export function TableOfContents({

useEffect(() => {
if (container.current) {
let allIds = headings.map(h => h.id);
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const targetId: string | null = entry.target.getAttribute('id');
Expand All @@ -57,7 +57,7 @@ export function TableOfContents({
});
return () => observer.disconnect();
}
}, [container]);
}, [container, headings]);

// catch if we're in a long gap between headings and resolve to the last available.
let activeId = visibleIds[0] || lastVisibleId;
Expand Down
1 change: 1 addition & 0 deletions docs/components/primitives/Emoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function Emoji({ symbol, alt, ...props }: EmojiProps) {
const posRef = useRef<HTMLElement>(null);
const [showOnTop, setShownTop] = useState(true);

// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (posRef.current && posRef.current.offsetTop - window.pageYOffset < 50) {
setShownTop(false);
Expand Down
4 changes: 2 additions & 2 deletions packages/auth/src/pages/InitPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const InitPage = ({ fieldPaths, listKey, enableWelcome }: InitPageProps) => {
fields[fieldPath] = adminMeta.lists[listKey].fields[fieldPath];
});
return fields;
}, [fieldPaths]);
}, [fieldPaths, adminMeta.lists, listKey]);

const [value, setValue] = useState(() => {
let state: Record<string, any> = {};
Expand Down Expand Up @@ -211,7 +211,7 @@ const InitPage = ({ fieldPaths, listKey, enableWelcome }: InitPageProps) => {
router.push((router.query.from as string | undefined) || '/');
}
}
}, [rawKeystone.authenticatedItem, router.query.from]);
}, [rawKeystone.authenticatedItem, enableWelcome, router]);

return mode === 'init' ? (
<SigninContainer>
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/src/pages/SigninPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const SigninPage = ({
if (rawKeystone.authenticatedItem.state === 'authenticated') {
router.push((router.query.from as string | undefined) || '/');
}
}, [rawKeystone.authenticatedItem, router.query.from]);
}, [rawKeystone.authenticatedItem, router]);
return (
<SigninContainer>
<Stack
Expand Down
1 change: 1 addition & 0 deletions packages/cloudinary/src/views/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function Field({
// Generate a random input key when the value changes, to ensure the file input is unmounted and
// remounted (this is the only way to reset its value and ensure onChange will fire again if
// the user selects the same file again)
// eslint-disable-next-line react-hooks/exhaustive-deps
const inputKey = useMemo(() => Math.random(), [value]);

return (
Expand Down
14 changes: 7 additions & 7 deletions packages/fields-document/src/DocumentEditor/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export function Toolbar({
documentFeatures: DocumentFeatures;
viewState?: { expanded: boolean; toggle: () => void };
}) {
const ExpandIcon = viewState?.expanded ? Minimize2Icon : Maximize2Icon;
const relationship = useContext(DocumentFieldRelationshipsContext);
const blockComponent = useContext(ComponentBlockContext);
const hasBlockItems = Object.entries(relationship).length || Object.keys(blockComponent).length;
Expand Down Expand Up @@ -119,8 +118,9 @@ export function Toolbar({
{!!hasBlockItems && <InsertBlockMenu />}

<ToolbarSeparator />
{useMemo(
() =>
{useMemo(() => {
const ExpandIcon = viewState?.expanded ? Minimize2Icon : Maximize2Icon;
return (
viewState && (
<Tooltip content={viewState.expanded ? 'Collapse' : 'Expand'} weight="subtle">
{attrs => (
Expand All @@ -135,9 +135,9 @@ export function Toolbar({
</ToolbarButton>
)}
</Tooltip>
),
[viewState]
)}
)
);
}, [viewState])}
</ToolbarContainer>
);
}
Expand Down Expand Up @@ -172,7 +172,7 @@ const MarkButton = forwardRef<any, { children: ReactNode; type: Mark }>(function
{...restProps}
/>
);
}, [editor, isDisabled, isSelected, props]);
}, [editor, isDisabled, isSelected, props, ref]);
});

const ToolbarContainer = ({ children }: { children: ReactNode }) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/fields-document/src/DocumentEditor/blockquote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const BlockquoteButton = ({
<QuoteIcon />
</ToolbarButton>
),
[attrs, isDisabled, isSelected]
[editor, attrs, isDisabled, isSelected]
);
};
export const blockquoteButton = (
Expand Down
2 changes: 1 addition & 1 deletion packages/fields-document/src/DocumentEditor/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function CodeButton({ attrs }: { attrs: {} }) {
<CodeIcon size="small" />
</ToolbarButton>
),
[isDisabled, isSelected, attrs]
[isDisabled, isSelected, attrs, editor]
);
}

Expand Down
5 changes: 3 additions & 2 deletions packages/fields-document/src/DocumentEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export function DocumentEditor({
const [expanded, setExpanded] = useState(false);
const editor = useMemo(
() => createDocumentEditor(documentFeatures, componentBlocks, relationships, isShiftPressedRef),
[documentFeatures, componentBlocks, relationships]
[documentFeatures, componentBlocks, relationships, isShiftPressedRef]
);

return (
Expand Down Expand Up @@ -309,6 +309,7 @@ export function DocumentEditorProvider({
relationships: Relationships;
documentFeatures: DocumentFeatures;
}) {
// eslint-disable-next-line react-hooks/exhaustive-deps
const identity = useMemo(() => Math.random().toString(36), [editor]);
return (
<Slate
Expand Down Expand Up @@ -392,7 +393,7 @@ export function DocumentEditorEditable({
}
return decorations;
},
[editor]
[editor, componentBlocks]
)}
css={styles}
autoFocus={autoFocus}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export function InsertMenu({ children, text }: { children: ReactNode; text: Text
return;
}
case 'Escape': {
const path = ReactEditor.findPath(editor, text);
const path = ReactEditor.findPath(editor, stateRef.current.text);
Transforms.unsetNodes(editor, 'insertMenu', { at: path });
event.preventDefault();
return;
Expand Down
2 changes: 1 addition & 1 deletion packages/fields-document/src/DocumentEditor/lists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const ListButton = forwardRef<
{...restProps}
/>
);
}, [props, ref, isDisabled, isSelected]);
}, [props, ref, isDisabled, isSelected, editor]);
});

export function nestList(editor: Editor) {
Expand Down
1 change: 1 addition & 0 deletions packages/fields/src/types/file/views/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function Field({
// Generate a random input key when the value changes, to ensure the file input is unmounted and
// remounted (this is the only way to reset its value and ensure onChange will fire again if
// the user selects the same file again)
// eslint-disable-next-line react-hooks/exhaustive-deps
const inputKey = useMemo(() => Math.random(), [value]);

return (
Expand Down
1 change: 1 addition & 0 deletions packages/fields/src/types/image/views/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export function Field({
// Generate a random input key when the value changes, to ensure the file input is unmounted and
// remounted (this is the only way to reset its value and ensure onChange will fire again if
// the user selects the same file again)
// eslint-disable-next-line react-hooks/exhaustive-deps
const inputKey = useMemo(() => Math.random(), [value]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ function ItemForm({
});
});
});
const labelFieldValue = itemGetter.data?.[list.labelField];
const itemId = itemGetter.data?.id!;
return (
<Box marginTop="xlarge">
<GraphQLErrorNotice
Expand Down Expand Up @@ -200,11 +202,11 @@ function ItemForm({
showDelete ? (
<DeleteButton
list={list}
itemLabel={(itemGetter.data?.[list.labelField] ?? itemGetter.data?.id!) as string}
itemId={itemGetter.data?.id!}
itemLabel={(labelFieldValue ?? itemId) as string}
itemId={itemId}
/>
) : undefined,
[showDelete, list, itemGetter.data?.[list.labelField], itemGetter.data?.id]
[showDelete, list, labelFieldValue, itemId]
)}
/>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,41 +74,46 @@ let listMetaGraphqlQuery: TypedDocumentNode<
}
`;

const storeableQueries = ['sortBy', 'fields'];

function useQueryParamsFromLocalStorage(listKey: string) {
const router = useRouter();
const localStorageKey = `keystone.list.${listKey}.list.page.info`;
const storeableQueries = ['sortBy', 'fields'];

const resetToDefaults = () => {
localStorage.removeItem(`keystone.list.${listKey}.list.page.info`);
localStorage.removeItem(localStorageKey);
router.replace({
pathname: router.pathname,
});
};

// GET QUERY FROM CACHE IF CONDITIONS ARE RIGHT
// MERGE QUERY PARAMS FROM CACHE WITH QUERY PARAMS FROM ROUTER
useEffect(() => {
let hasSomeQueryParamsWhichAreAboutListPage = Object.keys(router.query).some(x => {
return x.startsWith('!') || storeableQueries.includes(x);
});

if (!hasSomeQueryParamsWhichAreAboutListPage && router.isReady) {
const queryParamsFromLocalStorage = localStorage.getItem(localStorageKey);
let parsed;
try {
parsed = JSON.parse(queryParamsFromLocalStorage!);
} catch (err) {}
if (parsed) {
router.replace({
query: {
...router.query,
...parsed,
},
});
useEffect(
() => {
let hasSomeQueryParamsWhichAreAboutListPage = Object.keys(router.query).some(x => {
return x.startsWith('!') || storeableQueries.includes(x);
});

if (!hasSomeQueryParamsWhichAreAboutListPage && router.isReady) {
const queryParamsFromLocalStorage = localStorage.getItem(localStorageKey);
let parsed;
try {
parsed = JSON.parse(queryParamsFromLocalStorage!);
} catch (err) {}
if (parsed) {
router.replace({
query: {
...router.query,
...parsed,
},
});
}
}
}
}, [localStorageKey, router.isReady]);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[localStorageKey, router.isReady]
);
useEffect(() => {
let queryParamsToSerialize: Record<string, string> = {};
Object.keys(router.query).forEach(key => {
Expand All @@ -117,12 +122,9 @@ function useQueryParamsFromLocalStorage(listKey: string) {
}
});
if (Object.keys(queryParamsToSerialize).length) {
localStorage.setItem(
`keystone.list.${listKey}.list.page.info`,
JSON.stringify(queryParamsToSerialize)
);
localStorage.setItem(localStorageKey, JSON.stringify(queryParamsToSerialize));
} else {
localStorage.removeItem(`keystone.list.${listKey}.list.page.info`);
localStorage.removeItem(localStorageKey);
}
}, [localStorageKey, router]);

Expand Down Expand Up @@ -440,7 +442,7 @@ function DeleteManyButton({
}
}
`,
[list, selectedItems]
[list]
)
);
const [isOpen, setIsOpen] = useState(false);
Expand Down
Loading

1 comment on commit a11e54d

@vercel
Copy link

@vercel vercel bot commented on a11e54d Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.