diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1b16baf5..cb02f7de 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,6 +2,7 @@ module.exports = { extends: [ "eslint:recommended", "plugin:react/recommended", + "plugin:react-hooks/recommended", "plugin:import/recommended", "plugin:jsx-a11y/recommended", "plugin:@typescript-eslint/recommended", diff --git a/package-lock.json b/package-lock.json index 0c2c546e..d26efdea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.21", "postcss-nesting": "^11.2.1", "prettier": "^2.8.4", @@ -3823,6 +3824,18 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -10191,6 +10204,13 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/package.json b/package.json index a9e24931..f1a7619f 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.21", "postcss-nesting": "^11.2.1", "prettier": "^2.8.4", diff --git a/src-web/components/GraphQLEditor.tsx b/src-web/components/GraphQLEditor.tsx index 5b7a1840..f1bd5dfc 100644 --- a/src-web/components/GraphQLEditor.tsx +++ b/src-web/components/GraphQLEditor.tsx @@ -43,7 +43,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi const handleChangeQuery = useCallback( (query: string) => handleChange({ query, variables }), - [handleChange], + [handleChange, variables], ); const handleChangeVariables = useCallback( @@ -54,11 +54,12 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi // Meh, not much we can do here } }, - [handleChange], + [handleChange, query], ); const editorViewRef = useRef(null); + // Refetch the schema when the URL changes useEffect(() => { let unmounted = false; const body = JSON.stringify({ @@ -67,43 +68,44 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi }); sendEphemeralRequest({ ...baseRequest, body }).then((response) => { if (unmounted) return; + if (!editorViewRef.current) return; try { - if (editorViewRef.current) { - const { data } = JSON.parse(response.body); - const schema = buildClientSchema(data); - updateSchema(editorViewRef.current, schema); - } + const { data } = JSON.parse(response.body); + const schema = buildClientSchema(data); + console.log('SET SCHEMA', schema, baseRequest.url); + updateSchema(editorViewRef.current, schema); } catch (err) { console.log('Failed to parse introspection query', err); + updateSchema(editorViewRef.current, undefined); return; } }); return () => { unmounted = true; }; - }, [baseRequest.url]); + }, [baseRequest, baseRequest.url]); return (

Variables

diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index 63a11083..74a676a5 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -116,18 +116,16 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN }, }, ], - [ - activeRequest?.bodyType, - activeRequest?.headers, - activeRequest?.authenticationType, - activeRequest?.authentication, - ], + [activeRequest, updateRequest], ); - const handleBodyChange = useCallback((body: string) => updateRequest.mutate({ body }), []); + const handleBodyChange = useCallback( + (body: string) => updateRequest.mutate({ body }), + [updateRequest], + ); const handleHeadersChange = useCallback( (headers: HttpHeader[]) => updateRequest.mutate({ headers }), - [], + [updateRequest], ); return ( diff --git a/src-web/components/RequestResponse.tsx b/src-web/components/RequestResponse.tsx index 1d328fef..9e2a368e 100644 --- a/src-web/components/RequestResponse.tsx +++ b/src-web/components/RequestResponse.tsx @@ -64,7 +64,7 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) { const handleReset = useCallback( () => (vertical ? heightKv.set(DEFAULT) : widthKv.set(DEFAULT)), - [vertical], + [heightKv, vertical, widthKv], ); const handleResizeStart = useCallback( @@ -110,7 +110,7 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) { document.documentElement.addEventListener('mouseup', moveState.current.up); setIsResizing(true); }, - [widthKv.value, heightKv.value, vertical], + [width, height, vertical, heightKv, widthKv], ); return ( diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 99aea0a9..61b62659 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -100,7 +100,7 @@ function SidebarItems({ updateRequest.mutate({ id: requestId, update }); } }, - [hoveredIndex, requests], + [hoveredIndex, requests, updateRequest], ); return ( @@ -139,10 +139,13 @@ const _SidebarItem = forwardRef(function SidebarItem( const updateRequest = useUpdateRequest(requestId); const [editing, setEditing] = useState(false); - const handleSubmitNameEdit = useCallback(async (el: HTMLInputElement) => { - await updateRequest.mutate((r) => ({ ...r, name: el.value })); - setEditing(false); - }, []); + const handleSubmitNameEdit = useCallback( + async (el: HTMLInputElement) => { + await updateRequest.mutate((r) => ({ ...r, name: el.value })); + setEditing(false); + }, + [updateRequest], + ); const handleFocus = useCallback((el: HTMLInputElement | null) => { el?.focus(); @@ -171,7 +174,7 @@ const _SidebarItem = forwardRef(function SidebarItem( break; } }, - [active], + [handleSubmitNameEdit], ); return ( diff --git a/src-web/components/SidebarActions.tsx b/src-web/components/SidebarActions.tsx index b83b2052..9fcb399b 100644 --- a/src-web/components/SidebarActions.tsx +++ b/src-web/components/SidebarActions.tsx @@ -8,7 +8,7 @@ export const SidebarActions = memo(function SidebarDisplayToggle() { const createRequest = useCreateRequest({ navigateAfter: true }); const handleCreateRequest = useCallback(() => { createRequest.mutate({ name: 'New Request' }); - }, []); + }, [createRequest]); return ( <> diff --git a/src-web/components/UrlBar.tsx b/src-web/components/UrlBar.tsx index 32b82113..0ca45957 100644 --- a/src-web/components/UrlBar.tsx +++ b/src-web/components/UrlBar.tsx @@ -17,8 +17,14 @@ type Props = Pick & { export const UrlBar = memo(function UrlBar({ id: requestId, url, method, className }: Props) { const sendRequest = useSendRequest(requestId); const updateRequest = useUpdateRequest(requestId); - const handleMethodChange = useCallback((method: string) => updateRequest.mutate({ method }), []); - const handleUrlChange = useCallback((url: string) => updateRequest.mutate({ url }), []); + const handleMethodChange = useCallback( + (method: string) => updateRequest.mutate({ method }), + [updateRequest], + ); + const handleUrlChange = useCallback( + (url: string) => updateRequest.mutate({ url }), + [updateRequest], + ); const loading = useIsResponseLoading(requestId); const { updateKey } = useRequestUpdateKey(requestId); diff --git a/src-web/components/Workspace.tsx b/src-web/components/Workspace.tsx index 4639f645..42849328 100644 --- a/src-web/components/Workspace.tsx +++ b/src-web/components/Workspace.tsx @@ -42,7 +42,7 @@ export default function Workspace() { setFloating(false); sidebar.show(); } - }, [windowSize.width]); + }, [sidebar, windowSize.width]); const unsub = () => { if (moveState.current !== null) { @@ -73,7 +73,7 @@ export default function Workspace() { document.documentElement.addEventListener('mouseup', moveState.current.up); setIsResizing(true); }, - [sidebar.width, sidebar.hidden], + [sidebar], ); const sideWidth = sidebar.hidden ? 0 : sidebar.width; diff --git a/src-web/components/WorkspaceActionsDropdown.tsx b/src-web/components/WorkspaceActionsDropdown.tsx index 5006a018..500bc510 100644 --- a/src-web/components/WorkspaceActionsDropdown.tsx +++ b/src-web/components/WorkspaceActionsDropdown.tsx @@ -65,7 +65,15 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN }, }, ]; - }, [workspaces, activeWorkspaceId]); + }, [ + workspaces, + activeWorkspaceId, + routes, + createWorkspace, + confirm, + activeWorkspace?.name, + deleteWorkspace, + ]); return ( diff --git a/src-web/components/core/Dropdown.tsx b/src-web/components/core/Dropdown.tsx index 89a89406..bdf1a8d8 100644 --- a/src-web/components/core/Dropdown.tsx +++ b/src-web/components/core/Dropdown.tsx @@ -54,7 +54,7 @@ export function Dropdown({ children, items }: DropdownProps) { const handleClose = useCallback(() => { setOpen(false); ref.current?.focus(); - }, [ref.current]); + }, []); useEffect(() => { ref.current?.setAttribute('aria-expanded', open.toString()); @@ -63,7 +63,7 @@ export function Dropdown({ children, items }: DropdownProps) { const triggerRect = useMemo(() => { if (!open) return null; return ref.current?.getBoundingClientRect(); - }, [ref.current, open]); + }, [open]); return ( <> @@ -83,8 +83,6 @@ interface MenuProps { } function Menu({ className, items, onClose, triggerRect }: MenuProps) { - if (triggerRect === undefined) return null; - const containerRef = useRef(null); const [menuStyles, setMenuStyles] = useState({}); diff --git a/src-web/components/core/Editor/Editor.tsx b/src-web/components/core/Editor/Editor.tsx index 86a73d13..7f077a2e 100644 --- a/src-web/components/core/Editor/Editor.tsx +++ b/src-web/components/core/Editor/Editor.tsx @@ -88,12 +88,14 @@ const _Editor = forwardRef(function Editor( const { view, languageCompartment } = cm.current; const ext = getLanguageExtension({ contentType, useTemplating, autocomplete }); view.dispatch({ effects: languageCompartment.reconfigure(ext) }); - }, [contentType, autocomplete]); + }, [contentType, autocomplete, useTemplating]); useEffect(() => { if (cm.current === null) return; const { view } = cm.current; view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: defaultValue ?? '' } }); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [forceUpdateKey]); // Initialize the editor when ref mounts @@ -133,6 +135,8 @@ const _Editor = forwardRef(function Editor( } catch (e) { console.log('Failed to initialize Codemirror', e); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const cmContainer = ( diff --git a/src-web/components/core/IconButton.tsx b/src-web/components/core/IconButton.tsx index e22a690d..26f4fd2d 100644 --- a/src-web/components/core/IconButton.tsx +++ b/src-web/components/core/IconButton.tsx @@ -36,7 +36,7 @@ const _IconButton = forwardRef(function IconButton( if (showConfirm) setConfirmed(); onClick?.(e); }, - [onClick], + [onClick, setConfirmed, showConfirm], ); return (