diff --git a/.eslintrc.js b/.eslintrc.js index ce20b001b1..c43c3f252e 100755 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,21 +1,10 @@ const jsRules = { 'no-empty-pattern': 1, 'arrow-body-style': [1, 'as-needed', {requireReturnForObjectLiteral: true}], - 'arrow-parens': [1, 'always'], - 'arrow-spacing': 1, - 'block-spacing': [1, 'never'], - 'brace-style': [1, '1tbs', {allowSingleLine: true}], - 'comma-dangle': [1, 'always-multiline'], - 'comma-spacing': [1, {before: false, after: true}], curly: 1, - 'eol-last': [1, 'always'], eqeqeq: 1, - 'func-call-spacing': 1, - 'jsx-quotes': ['warn', 'prefer-single'], - 'key-spacing': [1, {beforeColon: false, afterColon: true, mode: 'strict'}], 'max-nested-callbacks': [1, 6], 'no-case-declarations': 1, - 'no-confusing-arrow': 1, 'no-console': [1, {allow: ['info', 'warn', 'error']}], 'no-control-regex': 0, 'no-debugger': 1, @@ -23,17 +12,12 @@ const jsRules = { 'no-extend-native': 1, 'no-extra-bind': 1, 'no-extra-boolean-cast': 1, - 'no-extra-semi': 1, 'no-fallthrough': 1, 'no-inner-declarations': 1, 'no-irregular-whitespace': 1, 'no-lonely-if': 1, - 'no-mixed-spaces-and-tabs': 1, - 'no-multi-spaces': 1, - 'no-multiple-empty-lines': [1, {max: 2, maxEOF: 1}], 'no-nonoctal-decimal-escape': 1, 'no-prototype-builtins': 1, - 'no-trailing-spaces': 1, 'no-undef-init': 1, 'no-undef': 1, 'no-unexpected-multiline': 1, @@ -44,12 +28,10 @@ const jsRules = { 'no-useless-escape': 1, 'no-useless-rename': 1, 'no-var': 1, - 'no-whitespace-before-property': 1, 'one-var': ['warn', 'never'], 'prefer-const': 1, 'prefer-rest-params': 1, 'prefer-spread': 1, - quotes: ['warn', 'single', {avoidEscape: true}], 'react/jsx-boolean-value': 1, 'react/jsx-no-undef': 2, 'react/jsx-sort-prop-types': 0, @@ -64,13 +46,87 @@ const jsRules = { 'react/react-in-jsx-scope': 2, 'react/self-closing-comp': 2, 'react/wrap-multilines': 0, + strict: 1, + + // Formatting (stylistic) rules. + // + // 'Deprecated' in ESLint 8.53.0, see announcement + // - Announcement: https://eslint.org/blog/2023/10/deprecating-formatting-rules/ + // - Rules deprecated: (https://github.com/eslint/eslint/commit/528e1c00dc2a) + // + // Successor: ESLint Stylistic, community project + // - Rules: https://eslint.style/packages/default#rules + // + // It'll be useful to keep a variant of these rules around as an alternative + // "auto-fixer" (versus Prettier), since Prettier modifies line wrappings in + // a way that can harm code readability at times. + // + // Idea: + // * Create a 'stylistic-only' ESLint config with these rules + // * Use the stylistic config for 'auto-fix' (lightweight formatting) in editor + // (normalize quotes, commas, semicolons, etc. without touching linebreaks) + // * Remove 'stylistic' rules from the main config, to reduce diagnostic + // clutter in code or in the 'Problems' pane + // * Keep an optional well-configured 'Prettier' tooling, able to run on + // modified lines or current/selected paragraph, around for convenience + 'arrow-parens': [1, 'always'], + 'arrow-spacing': 1, + 'block-spacing': [1, 'never'], + 'brace-style': [1, '1tbs', {allowSingleLine: true}], + 'comma-dangle': [1, 'always-multiline'], + 'comma-spacing': [1, {before: false, after: true}], + 'eol-last': [1, 'always'], + 'func-call-spacing': 1, + 'jsx-quotes': ['warn', 'prefer-single'], + 'key-spacing': [1, {beforeColon: false, afterColon: true, mode: 'strict'}], + 'no-confusing-arrow': 1, + 'no-extra-semi': 1, + 'no-mixed-spaces-and-tabs': 1, + 'no-multi-spaces': 1, // Comment out to permit columnar formatting + 'no-multiple-empty-lines': [1, {max: 2, maxEOF: 1}], + 'no-trailing-spaces': 1, + 'no-whitespace-before-property': 1, + 'quotes': ['warn', 'single', {avoidEscape: true}], 'semi-spacing': [1, {before: false, after: true}], 'semi-style': [1, 'last'], - semi: 1, + 'semi': 1, 'space-before-function-paren': 'off', 'space-in-parens': [1, 'never'], 'space-infix-ops': 1, - strict: 1, + + // React Plugin - Default to plugin:react/recommended. + // https://github.com/jsx-eslint/eslint-plugin-react + + // TODO: Set these rules back to 'error' severity and fix the affected code + // (1) comment these out (2) run npx eslint (...) with --quiet to list only errors + // =============================================================================== + // Deprecated API + 'react/no-deprecated': 1, // e.g. componentWillReceiveProps + 'react/no-find-dom-node': 1, // Do not use findDOMNode + 'react/no-string-refs': 1, // Using string literals in ref attributes is deprecated + // Misuse + 'react/jsx-key': 1, // Missing "key" prop for element in iterator + 'react/no-direct-mutation-state': 1, // Do not mutate state directly. Use setState() + // Other + 'react/display-name': 1, // "Component definition is missing display name" + 'react/no-unescaped-entities': 1, // e.g. `"` can be escaped with `"`… + + // OK + // == + 'react/jsx-no-target-blank': 0, // Using target="_blank" without rel="noreferrer" + // …not a problem for modern browsers; see 2021 update + // (https://mathiasbynens.github.io/rel-noopener/#recommendations) + + + // React Hooks - Default to plugin:react-hooks/recommended + // https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks + + // TODO: Fix these! They should be errors, too. + 'react-hooks/rules-of-hooks': 1, // TODO: make this an error, not warning + + // React Query - Default to plugin:@tanstack/eslint-plugin-query/recommended + // https://tanstack.com/query/latest/docs/eslint/eslint-plugin-query + }; // TypeScript rules override some of JavaScript rules plus add a few more. @@ -79,19 +135,8 @@ const tsRules = Object.assign({}, jsRules, { // Would be good to enable in future, when most of codebase is TS. '@typescript-eslint/ban-types': 'off', '@typescript-eslint/ban-ts-comment': 1, - '@typescript-eslint/comma-dangle': [1, 'always-multiline'], - '@typescript-eslint/comma-spacing': [1, {before: false, after: true}], '@typescript-eslint/consistent-type-definitions': [1, 'interface'], '@typescript-eslint/consistent-type-imports': [1, {prefer: 'type-imports'}], - '@typescript-eslint/func-call-spacing': [1, 'never'], - '@typescript-eslint/keyword-spacing': [1, {before: true, after: true}], - '@typescript-eslint/member-delimiter-style': [ - 1, - { - multiline: {delimiter: 'semi'}, - singleline: {delimiter: 'semi'}, - }, - ], '@typescript-eslint/method-signature-style': [1, 'property'], '@typescript-eslint/naming-convention': [ 1, @@ -127,8 +172,6 @@ const tsRules = Object.assign({}, jsRules, { '@typescript-eslint/no-empty-function': 1, '@typescript-eslint/no-empty-interface': 1, '@typescript-eslint/no-explicit-any': 1, - '@typescript-eslint/no-extra-parens': 'off', - '@typescript-eslint/no-extra-semi': 1, '@typescript-eslint/no-inferrable-types': 1, '@typescript-eslint/no-invalid-void-type': 1, '@typescript-eslint/no-loop-func': 1, @@ -139,38 +182,11 @@ const tsRules = Object.assign({}, jsRules, { '@typescript-eslint/no-use-before-define': 1, '@typescript-eslint/no-useless-constructor': 1, '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/object-curly-spacing': 1, '@typescript-eslint/parameter-properties': 1, '@typescript-eslint/prefer-enum-initializers': 1, '@typescript-eslint/prefer-optional-chain': 1, - '@typescript-eslint/quotes': ['warn', 'single', {avoidEscape: true}], - '@typescript-eslint/semi': 1, '@typescript-eslint/sort-type-union-intersection-members': 'off', - '@typescript-eslint/space-before-function-paren': [ - 1, - { - anonymous: 'always', - named: 'never', - asyncArrow: 'always', // TODO: Defer all formatting rules to Prettier - }, - ], - '@typescript-eslint/type-annotation-spacing': [ - 1, - { - before: false, - after: true, - overrides: { - arrow: { - before: true, - after: true, - }, - }, - }, - ], '@typescript-eslint/unified-signatures': 1, - 'comma-dangle': 'off', - 'comma-spacing': 'off', - 'func-call-spacing': 'off', // The 'import' plugin supports separately importing types // (@typescript-eslint/no-duplicate-imports is deprecated) 'import/no-duplicates': 1, @@ -185,8 +201,47 @@ const tsRules = Object.assign({}, jsRules, { 'no-unused-vars': 'off', 'no-useless-backreference': 'off', 'no-var': 1, - quotes: 'off', - semi: 'off', + + // Formatting (stylistic) rules, for TypeScript. + // 'Deprecated' in @typescript-eslint v6.16.0 (https://typescript-eslint.io/blog/deprecating-formatting-rules/), + // Still useful for us in some form, particularly with auto-fix. + // See "Formatting (stylistic) rules" comment, above. + '@typescript-eslint/comma-dangle': [1, 'always-multiline'], + '@typescript-eslint/comma-spacing': [1, {before: false, after: true}], + '@typescript-eslint/func-call-spacing': [1, 'never'], + '@typescript-eslint/keyword-spacing': [1, {before: true, after: true}], + '@typescript-eslint/no-extra-parens': 'off', + '@typescript-eslint/no-extra-semi': 1, + '@typescript-eslint/object-curly-spacing': 1, + '@typescript-eslint/member-delimiter-style': [1, { + multiline: {delimiter: 'semi'}, + singleline: {delimiter: 'semi'}, + }], + '@typescript-eslint/quotes': ['warn', 'single', {avoidEscape: true}], + '@typescript-eslint/semi': 1, + '@typescript-eslint/type-annotation-spacing': [1, { + before: false, + after: true, + overrides: { + arrow: { + before: true, + after: true, + }, + }, + }], + '@typescript-eslint/space-before-function-paren': [1, { + anonymous: 'always', + named: 'never', + asyncArrow: 'always', + }], + // Turn off equivalent ESLint rules + 'comma-dangle': 'off', + 'comma-spacing': 'off', + 'func-call-spacing': 'off', + 'no-extra-semi': 'off', + 'quotes': 'off', + 'semi': 'off', + }); module.exports = { @@ -206,12 +261,27 @@ module.exports = { }, ignorePatterns: ['**/*.scss'], plugins: ['react'], - extends: ['eslint:recommended', 'prettier', 'plugin:storybook/recommended'], + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', // Use recommended rules: https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules + 'plugin:react/jsx-runtime', // Use the new JSX transform + 'plugin:react-hooks/recommended', // Rules of Hooks (https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks) + 'plugin:@tanstack/eslint-plugin-query/recommended', // For Tanstack Query (aka react-query) + 'prettier', + 'plugin:storybook/recommended'], rules: jsRules, settings: { react: { version: 'detect', }, + // https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc- + // Try to include all the component wrapper functions we use here + componentWrapperFunctions: [ + 'observer', // MobX observer + ], + linkComponenets: [ + { name: 'Link', linkAttribute: 'to' }, // React Router Link + ], }, overrides: [ { diff --git a/jsapp/js/alertify.ts b/jsapp/js/alertify.ts index 499dd0c6c9..cb3937af7c 100644 --- a/jsapp/js/alertify.ts +++ b/jsapp/js/alertify.ts @@ -4,7 +4,8 @@ import {KeyNames} from 'js/constants'; import type {IconName} from 'jsapp/fonts/k-icons'; import {escapeHtml} from 'js/utils'; import type {ReactElement} from 'react'; -import {render} from 'react-dom'; +import {createRoot} from 'react-dom/client'; + interface MultiConfirmButton { label: string; @@ -193,6 +194,9 @@ export function destroyConfirm( */ export function renderJSXMessage(jsx: ReactElement) { const domNode = document.createElement('div'); - render(jsx, domNode); - return domNode.outerHTML; + const root = createRoot(domNode); + root.render(jsx); + const str = domNode.outerHTML; + root.unmount(); + return str; } diff --git a/jsapp/js/app.jsx b/jsapp/js/app.jsx index a0893685ad..a061b5a61a 100644 --- a/jsapp/js/app.jsx +++ b/jsapp/js/app.jsx @@ -29,6 +29,11 @@ import { import {isAnyProcessingRouteActive} from 'js/components/processing/routes.utils'; import pageState from 'js/pageState.store'; +// Query-related +import { QueryClientProvider } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import { queryClient } from './query/queryClient.ts' + class App extends React.Component { constructor(props) { super(props); @@ -100,7 +105,7 @@ class App extends React.Component { // opt for a more sane, and singluar(!) solution. return ( - <> + @@ -139,7 +144,23 @@ class App extends React.Component { - + + + {/* React Query Devtools - GUI for inspecting and modifying query status + (https://tanstack.com/query/latest/docs/framework/react/devtools) + They only show up in dev server (NODE_ENV==='development') + Additionally, we're keeping them commented out in `beta` + (https://github.com/kobotoolbox/kpi/pull/5001#discussion_r1691067344) + (1) Uncomment if you want to use these tools + (2) The + + */} + + + ); } diff --git a/jsapp/js/formbuild/renderInBackbone.es6 b/jsapp/js/formbuild/renderInBackbone.es6 index 3daa304eb7..2e13bebdda 100644 --- a/jsapp/js/formbuild/renderInBackbone.es6 +++ b/jsapp/js/formbuild/renderInBackbone.es6 @@ -47,4 +47,8 @@ export function renderKobomatrix (view, el) { let model = new KoboMatrixRow(view.model); const root = createRoot(el.get(0)); root.render(); + // TODO: should this root be unmounted at some point? + // https://react.dev/reference/react-dom/client/createRoot#root-unmount + // Maybe instantiate the root in KoboMatrixView, then unmount it when + // KoboMatrixView is disposed. } diff --git a/jsapp/js/query/queryClient.ts b/jsapp/js/query/queryClient.ts new file mode 100644 index 0000000000..372b611a29 --- /dev/null +++ b/jsapp/js/query/queryClient.ts @@ -0,0 +1,8 @@ +import { QueryClient } from '@tanstack/react-query' + +// Some shared defaults and config can be set here! +// Docs: https://tanstack.com/query/v5/docs/reference/QueryClient#queryclient +// See: https://tanstack.com/query/v5/docs/framework/react/guides/important-defaults +const queryClient = new QueryClient() + +export { queryClient } diff --git a/package-lock.json b/package-lock.json index fcaa48dfe9..863cd325cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "chai-deep-equal-ignore-undefined": "^1.1.1", "classnames": "^2.3.1", "fuse.js": "^6.4.3", + "immer": "^10.1.1", "immutable": "^3.8.2", "jquery": "^3.5.1", "jquery-ui": "1.12.1", @@ -72,6 +73,8 @@ "reflux-core": "^1.0.0", "select2": "3.5.2-browserify", "spark-md5": "^3.0.2", + "use-immer": "^0.10.0", + "wretch": "^2.9.0", "zxcvbn": "^4.4.2" }, "devDependencies": { @@ -86,6 +89,8 @@ "@storybook/react-webpack5": "^7.0.24", "@storybook/testing-library": "^0.2.0", "@swc/core": "^1.6.6", + "@tanstack/eslint-plugin-query": "^5.50.1", + "@tanstack/react-query-devtools": "^5.50.1", "@types/chai": "^4.3.1", "@types/gtag.js": "^0.0.12", "@types/jquery": "^3.5.10", @@ -132,7 +137,8 @@ "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-storybook": "^0.6.15", "fork-ts-checker-webpack-plugin": "^9.0.2", "mocha": "^7.2.0", @@ -7054,21 +7060,47 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.50.1.tgz", + "integrity": "sha512-k4Ra3hx8ddQ1YjW6+5T3f2OuphAipR5cHonB7nQSM5JbiJ7fAo2iu/y4eP3MRgtlrxN//Da77Xg5jqNafrjEHA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "8.0.0-alpha.30" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8 || ^9" + } + }, "node_modules/@tanstack/query-core": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.49.1.tgz", - "integrity": "sha512-JnC9ndmD1KKS01Rt/ovRUB1tmwO7zkyXAyIxN9mznuJrcNtOrkmOnQqdJF2ib9oHzc2VxHomnEG7xyfo54Npkw==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.50.1.tgz", + "integrity": "sha512-lpfhKPrJlyV2DSVcQb/HuozH3Av3kws4ge22agx+lNGpFkS4vLZ7St0l3GLwlAD+bqB+qXGex3JdRKUNtMviEQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.50.1.tgz", + "integrity": "sha512-MQ5JK3yRwBP1SRuwoJVPGZP4cMLXCQ0t+6blDbcAVGEoqrEuvbgTdwlN729AKBR0hidOWPFR9n5YpI2Y8bBZOQ==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "5.49.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.49.2.tgz", - "integrity": "sha512-6rfwXDK9BvmHISbNFuGd+wY3P44lyW7lWiA9vIFGT/T0P9aHD1VkjTvcM4SDAIbAQ9ygEZZoLt7dlU1o3NjMVA==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.50.1.tgz", + "integrity": "sha512-s0DW3rVBDPReDDovUjVqItVa3R2nPfUANK9nqGvarO2DwTiY9U4EBTsqizMxItRCoGgK5apeM7D3mxlHrSKpdQ==", "dependencies": { - "@tanstack/query-core": "5.49.1" + "@tanstack/query-core": "5.50.1" }, "funding": { "type": "github", @@ -7078,6 +7110,23 @@ "react": "^18.0.0" } }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.50.1.tgz", + "integrity": "sha512-zgPmEFv9GhLAx6eaf9r0ACbcxit1ZSuv/uPpOXBTTSPLijlWcfpQTOdZx0jYQ14t2cUfWjrAW41cUmcCvT4X/g==", + "dev": true, + "dependencies": { + "@tanstack/query-devtools": "5.50.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.50.1", + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", @@ -12449,6 +12498,18 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "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/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -14809,6 +14870,15 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", @@ -24993,6 +25063,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/use-immer": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.10.0.tgz", + "integrity": "sha512-/eVwNR4TG9Tm/dd+aHYLLaI0FLfYKlkTqKMkn78Ah/EYVzWd/zJIgpkdoFEKbhQJOGo8XN7/mWrTx0exp1c+Ug==", + "peerDependencies": { + "immer": ">=8.0.0", + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", @@ -26068,6 +26147,14 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/wretch": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/wretch/-/wretch-2.9.0.tgz", + "integrity": "sha512-kKp1xWQO+Vh9I6RJq7Yers2KKrmF7LXB00c9knMDn3IdB7etcQOarrsHArpj1sZ1Dlav8ss6R1DuUpDSTqmQew==", + "engines": { + "node": ">=14" + } + }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", @@ -30863,17 +30950,41 @@ "@swc/counter": "^0.1.3" } }, + "@tanstack/eslint-plugin-query": { + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.50.1.tgz", + "integrity": "sha512-k4Ra3hx8ddQ1YjW6+5T3f2OuphAipR5cHonB7nQSM5JbiJ7fAo2iu/y4eP3MRgtlrxN//Da77Xg5jqNafrjEHA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^6" + } + }, "@tanstack/query-core": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.49.1.tgz", - "integrity": "sha512-JnC9ndmD1KKS01Rt/ovRUB1tmwO7zkyXAyIxN9mznuJrcNtOrkmOnQqdJF2ib9oHzc2VxHomnEG7xyfo54Npkw==" + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.50.1.tgz", + "integrity": "sha512-lpfhKPrJlyV2DSVcQb/HuozH3Av3kws4ge22agx+lNGpFkS4vLZ7St0l3GLwlAD+bqB+qXGex3JdRKUNtMviEQ==" + }, + "@tanstack/query-devtools": { + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.50.1.tgz", + "integrity": "sha512-MQ5JK3yRwBP1SRuwoJVPGZP4cMLXCQ0t+6blDbcAVGEoqrEuvbgTdwlN729AKBR0hidOWPFR9n5YpI2Y8bBZOQ==", + "dev": true }, "@tanstack/react-query": { - "version": "5.49.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.49.2.tgz", - "integrity": "sha512-6rfwXDK9BvmHISbNFuGd+wY3P44lyW7lWiA9vIFGT/T0P9aHD1VkjTvcM4SDAIbAQ9ygEZZoLt7dlU1o3NjMVA==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.50.1.tgz", + "integrity": "sha512-s0DW3rVBDPReDDovUjVqItVa3R2nPfUANK9nqGvarO2DwTiY9U4EBTsqizMxItRCoGgK5apeM7D3mxlHrSKpdQ==", + "requires": { + "@tanstack/query-core": "5.50.1" + } + }, + "@tanstack/react-query-devtools": { + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.50.1.tgz", + "integrity": "sha512-zgPmEFv9GhLAx6eaf9r0ACbcxit1ZSuv/uPpOXBTTSPLijlWcfpQTOdZx0jYQ14t2cUfWjrAW41cUmcCvT4X/g==", + "dev": true, "requires": { - "@tanstack/query-core": "5.49.1" + "@tanstack/query-devtools": "5.50.1" } }, "@testing-library/dom": { @@ -35264,6 +35375,13 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "requires": {} + }, "eslint-plugin-storybook": { "version": "0.6.15", "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.15.tgz", @@ -36889,6 +37007,11 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, + "immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==" + }, "immutable": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", @@ -44521,6 +44644,12 @@ "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", "requires": {} }, + "use-immer": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.10.0.tgz", + "integrity": "sha512-/eVwNR4TG9Tm/dd+aHYLLaI0FLfYKlkTqKMkn78Ah/EYVzWd/zJIgpkdoFEKbhQJOGo8XN7/mWrTx0exp1c+Ug==", + "requires": {} + }, "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", @@ -45284,6 +45413,11 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "wretch": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/wretch/-/wretch-2.9.0.tgz", + "integrity": "sha512-kKp1xWQO+Vh9I6RJq7Yers2KKrmF7LXB00c9knMDn3IdB7etcQOarrsHArpj1sZ1Dlav8ss6R1DuUpDSTqmQew==" + }, "write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", diff --git a/package.json b/package.json index 744b07769c..be0cea8227 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "chai-deep-equal-ignore-undefined": "^1.1.1", "classnames": "^2.3.1", "fuse.js": "^6.4.3", + "immer": "^10.1.1", "immutable": "^3.8.2", "jquery": "^3.5.1", "jquery-ui": "1.12.1", @@ -69,6 +70,8 @@ "reflux-core": "^1.0.0", "select2": "3.5.2-browserify", "spark-md5": "^3.0.2", + "use-immer": "^0.10.0", + "wretch": "^2.9.0", "zxcvbn": "^4.4.2" }, "devDependencies": { @@ -83,6 +86,8 @@ "@storybook/react-webpack5": "^7.0.24", "@storybook/testing-library": "^0.2.0", "@swc/core": "^1.6.6", + "@tanstack/eslint-plugin-query": "^5.50.1", + "@tanstack/react-query-devtools": "^5.50.1", "@types/chai": "^4.3.1", "@types/gtag.js": "^0.0.12", "@types/jquery": "^3.5.10", @@ -129,7 +134,8 @@ "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-storybook": "^0.6.15", "fork-ts-checker-webpack-plugin": "^9.0.2", "mocha": "^7.2.0", @@ -173,6 +179,11 @@ }, "storybook": { "giget": "1.1.3" + }, + "@tanstack/eslint-plugin-query": { + "@typescript-eslint/utils": "^6", + "@typescript-eslint/rule-tester": "^6", + "eslint": "$eslint" } }, "scripts": {