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": {