diff --git a/client/app/components/EditParameterSettingsDialog.jsx b/client/app/components/EditParameterSettingsDialog.jsx index b5af2fde635..19a67cf35e9 100644 --- a/client/app/components/EditParameterSettingsDialog.jsx +++ b/client/app/components/EditParameterSettingsDialog.jsx @@ -86,13 +86,13 @@ function EditParameterSettingsDialog(props) { // fetch query by id useEffect(() => { - const { queryId } = props.parameter; + const queryId = props.parameter.queryId; if (queryId) { Query.get({ id: queryId }, (query) => { setInitialQuery(query); }); } - }, [props.parameter]); + }, [props.parameter.queryId]); function isFulfilled() { // name diff --git a/client/app/components/QuerySelector.jsx b/client/app/components/QuerySelector.jsx index d56edeb890f..4e082846f76 100644 --- a/client/app/components/QuerySelector.jsx +++ b/client/app/components/QuerySelector.jsx @@ -2,22 +2,15 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { react2angular } from 'react2angular'; -import { debounce, find } from 'lodash'; +import { find } from 'lodash'; import Input from 'antd/lib/input'; import Select from 'antd/lib/select'; import { Query } from '@/services/query'; import notification from '@/services/notification'; import { QueryTagsControl } from '@/components/tags-control/TagsControl'; +import useSearchResults from '@/lib/hooks/useSearchResults'; -const SEARCH_DEBOUNCE_DURATION = 200; const { Option } = Select; - -class StaleSearchError extends Error { - constructor() { - super('stale search'); - } -} - function search(term) { // get recent if (!term) { @@ -34,17 +27,16 @@ function search(term) { } export function QuerySelector(props) { - const [searchTerm, setSearchTerm] = useState(); - const [searching, setSearching] = useState(); - const [searchResults, setSearchResults] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); const [selectedQuery, setSelectedQuery] = useState(); + const [doSearch, searchResults, searching] = useSearchResults(search, { initialResults: [] }); - let isStaleSearch = false; - const debouncedSearch = debounce(_search, SEARCH_DEBOUNCE_DURATION); const placeholder = 'Search a query by name'; const clearIcon = selectQuery(null)} />; const spinIcon = ; + useEffect(() => { doSearch(searchTerm); }, [doSearch, searchTerm]); + // set selected from prop useEffect(() => { if (props.selectedQuery) { @@ -52,43 +44,6 @@ export function QuerySelector(props) { } }, [props.selectedQuery]); - // on search term changed, debounced - useEffect(() => { - // clear results, no search - if (searchTerm === null) { - setSearchResults(null); - return () => {}; - } - - // search - debouncedSearch(searchTerm); - return () => { - debouncedSearch.cancel(); - isStaleSearch = true; - }; - }, [searchTerm]); - - function _search(term) { - setSearching(true); - search(term) - .then(rejectStale) - .then((results) => { - setSearchResults(results); - setSearching(false); - }) - .catch((err) => { - if (!(err instanceof StaleSearchError)) { - setSearching(false); - } - }); - } - - function rejectStale(results) { - return isStaleSearch - ? Promise.reject(new StaleSearchError()) - : Promise.resolve(results); - } - function selectQuery(queryId) { let query = null; if (queryId) { diff --git a/client/app/components/app-header/components/FavoritesDropdown.jsx b/client/app/components/app-header/components/FavoritesDropdown.jsx index 3849e43ea33..5e98a5f723c 100644 --- a/client/app/components/app-header/components/FavoritesDropdown.jsx +++ b/client/app/components/app-header/components/FavoritesDropdown.jsx @@ -27,7 +27,9 @@ export default function FavoritesDropdown({ fetch, urlTemplate }) { }, [fetch]); // fetch items on init - useEffect(() => fetchItems(false), [fetchItems]); + useEffect(() => { + fetchItems(false); + }, [fetchItems]); // fetch items on click const onVisibleChange = visible => visible && fetchItems(); diff --git a/client/app/lib/hooks/useQueryResult.js b/client/app/lib/hooks/useQueryResult.js index 43b420c223d..79dbea3c168 100644 --- a/client/app/lib/hooks/useQueryResult.js +++ b/client/app/lib/hooks/useQueryResult.js @@ -11,8 +11,8 @@ function getQueryResultData(queryResult) { export default function useQueryResult(queryResult) { const [data, setData] = useState(getQueryResultData(queryResult)); - let isCancelled = false; useEffect(() => { + let isCancelled = false; if (queryResult) { queryResult.toPromise() .then(() => { diff --git a/client/app/lib/hooks/useSearchResults.js b/client/app/lib/hooks/useSearchResults.js index 1252a2d714c..0e92accc069 100644 --- a/client/app/lib/hooks/useSearchResults.js +++ b/client/app/lib/hooks/useSearchResults.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useDebouncedCallback } from 'use-debounce'; export default function useSearchResults( @@ -7,17 +7,16 @@ export default function useSearchResults( ) { const [result, setResult] = useState(initialResults); const [isLoading, setIsLoading] = useState(false); - - let currentSearchTerm = null; - let isDestroyed = false; + const currentSearchTerm = useRef(null); + const isDestroyed = useRef(false); const [doSearch] = useDebouncedCallback((searchTerm) => { setIsLoading(true); - currentSearchTerm = searchTerm; + currentSearchTerm.current = searchTerm; fetch(searchTerm) .catch(() => null) .then((data) => { - if ((searchTerm === currentSearchTerm) && !isDestroyed) { + if ((searchTerm === currentSearchTerm.current) && !isDestroyed.current) { setResult(data); setIsLoading(false); } @@ -26,7 +25,7 @@ export default function useSearchResults( useEffect(() => ( // ignore all requests after component destruction - () => { isDestroyed = true; } + () => { isDestroyed.current = true; } ), []); return [doSearch, result, isLoading]; diff --git a/client/app/visualizations/choropleth/Renderer/index.jsx b/client/app/visualizations/choropleth/Renderer/index.jsx index 2d399005c44..8f28cfb4044 100644 --- a/client/app/visualizations/choropleth/Renderer/index.jsx +++ b/client/app/visualizations/choropleth/Renderer/index.jsx @@ -58,7 +58,7 @@ export default function Renderer({ data, options, onOptionsChange }) { options, // detect changes for all options except bounds, but pass them all! ); } - }, [map, geoJson, data, optionsWithoutBounds, options]); + }, [map, geoJson, data, optionsWithoutBounds]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (map) { diff --git a/client/app/visualizations/funnel/Renderer/index.jsx b/client/app/visualizations/funnel/Renderer/index.jsx index 43f5ccea61a..01186708fbf 100644 --- a/client/app/visualizations/funnel/Renderer/index.jsx +++ b/client/app/visualizations/funnel/Renderer/index.jsx @@ -16,7 +16,8 @@ function generateRowKeyPrefix() { export default function Renderer({ data, options }) { const funnelData = useMemo(() => prepareData(data.rows, options), [data, options]); - const rowKeyPrefix = useMemo(() => generateRowKeyPrefix(), []); + // eslint-disable-next-line react-hooks/exhaustive-deps + const rowKeyPrefix = useMemo(() => generateRowKeyPrefix(), [funnelData]); const formatValue = useMemo(() => createNumberFormatter(options.numberFormat), [options.numberFormat]); diff --git a/package-lock.json b/package-lock.json index a222b3a63ad..42b2bf9b324 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2526,7 +2526,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", @@ -2540,7 +2540,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -2758,7 +2758,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -5506,6 +5506,66 @@ } } }, + "eslint-loader": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-3.0.3.tgz", + "integrity": "sha512-+YRqB95PnNvxNp1HEjQmvf9KNvCin5HXYYseOXVC2U0KEcw4IkQ2IQEBG46j7+gW39bMzeu0GsUhVbBY3Votpw==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "loader-fs-cache": "^1.0.2", + "loader-utils": "^1.2.3", + "object-hash": "^2.0.1", + "schema-utils": "^2.6.1" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "schema-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "eslint-module-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", @@ -6544,7 +6604,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -9044,7 +9104,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -9376,7 +9436,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9470,7 +9530,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -10203,7 +10263,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-observable": { @@ -11449,6 +11509,57 @@ "strip-bom": "^3.0.0" } }, + "loader-fs-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz", + "integrity": "sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==", + "dev": true, + "requires": { + "find-cache-dir": "^0.1.1", + "mkdirp": "0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + } + } + } + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -11673,7 +11784,7 @@ }, "magic-string": { "version": "0.22.5", - "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", "requires": { "vlq": "^0.2.2" @@ -11785,12 +11896,12 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11983,7 +12094,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12179,7 +12290,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12680,7 +12791,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12906,6 +13017,12 @@ } } }, + "object-hash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.1.tgz", + "integrity": "sha512-HgcGMooY4JC2PBt9sdUdJ6PMzpin+YtY3r/7wg0uTifP+HJWW8rammseSEHuyt0UeShI183UGssCJqm1bJR7QA==", + "dev": true + }, "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", @@ -13076,7 +13193,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -16310,7 +16427,7 @@ "dependencies": { "minimist": { "version": "0.0.5", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=" } } @@ -16803,7 +16920,7 @@ }, "split": { "version": "0.2.10", - "resolved": "http://registry.npmjs.org/split/-/split-0.2.10.tgz", + "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", "requires": { "through": "2" @@ -17161,7 +17278,7 @@ "dependencies": { "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -17315,7 +17432,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { diff --git a/package.json b/package.json index 3604c62131b..93477c8a4c2 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "eslint": "^6.7.2", "eslint-config-prettier": "^6.7.0", "eslint-config-react-app": "^5.1.0", + "eslint-loader": "^3.0.3", "eslint-plugin-chai-friendly": "^0.5.0", "eslint-plugin-compat": "^3.3.0", "eslint-plugin-cypress": "^2.0.1",