Fix initial list settings (#1401)
* Allow ocalStorage values to be initialized by cb * Initialize list settings if not found * Fix settings merging * Refactor types
This commit is contained in:
parent
4fcc3eec5e
commit
51e204076f
9 changed files with 314 additions and 112 deletions
128
package-lock.json
generated
128
package-lock.json
generated
|
@ -6931,33 +6931,32 @@
|
||||||
"integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg=="
|
"integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg=="
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz",
|
||||||
"integrity": "sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==",
|
"integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/experimental-utils": "4.21.0",
|
"@typescript-eslint/experimental-utils": "4.31.0",
|
||||||
"@typescript-eslint/scope-manager": "4.21.0",
|
"@typescript-eslint/scope-manager": "4.31.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"lodash": "^4.17.15",
|
"regexpp": "^3.1.0",
|
||||||
"regexpp": "^3.0.0",
|
"semver": "^7.3.5",
|
||||||
"semver": "^7.3.2",
|
"tsutils": "^3.21.0"
|
||||||
"tsutils": "^3.17.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/experimental-utils": {
|
"@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz",
|
||||||
"integrity": "sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA==",
|
"integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/json-schema": "^7.0.3",
|
"@types/json-schema": "^7.0.7",
|
||||||
"@typescript-eslint/scope-manager": "4.21.0",
|
"@typescript-eslint/scope-manager": "4.31.0",
|
||||||
"@typescript-eslint/types": "4.21.0",
|
"@typescript-eslint/types": "4.31.0",
|
||||||
"@typescript-eslint/typescript-estree": "4.21.0",
|
"@typescript-eslint/typescript-estree": "4.31.0",
|
||||||
"eslint-scope": "^5.0.0",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-utils": "^2.0.0"
|
"eslint-utils": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
|
@ -6973,55 +6972,55 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz",
|
||||||
"integrity": "sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==",
|
"integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "4.21.0",
|
"@typescript-eslint/scope-manager": "4.31.0",
|
||||||
"@typescript-eslint/types": "4.21.0",
|
"@typescript-eslint/types": "4.31.0",
|
||||||
"@typescript-eslint/typescript-estree": "4.21.0",
|
"@typescript-eslint/typescript-estree": "4.31.0",
|
||||||
"debug": "^4.1.1"
|
"debug": "^4.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz",
|
||||||
"integrity": "sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw==",
|
"integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.21.0",
|
"@typescript-eslint/types": "4.31.0",
|
||||||
"@typescript-eslint/visitor-keys": "4.21.0"
|
"@typescript-eslint/visitor-keys": "4.31.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz",
|
||||||
"integrity": "sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w==",
|
"integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz",
|
||||||
"integrity": "sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w==",
|
"integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.21.0",
|
"@typescript-eslint/types": "4.31.0",
|
||||||
"@typescript-eslint/visitor-keys": "4.21.0",
|
"@typescript-eslint/visitor-keys": "4.31.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"globby": "^11.0.1",
|
"globby": "^11.0.3",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.5",
|
||||||
"tsutils": "^3.17.1"
|
"tsutils": "^3.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.21.0",
|
"version": "4.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz",
|
||||||
"integrity": "sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w==",
|
"integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.21.0",
|
"@typescript-eslint/types": "4.31.0",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14117,26 +14116,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-utils": {
|
"eslint-utils": {
|
||||||
"version": "2.1.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
|
||||||
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
|
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"eslint-visitor-keys": "^1.1.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"eslint-visitor-keys": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||||
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"espree": {
|
"espree": {
|
||||||
|
@ -25120,9 +25111,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"regexpp": {
|
"regexpp": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
|
||||||
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
|
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"regexpu-core": {
|
"regexpu-core": {
|
||||||
|
@ -29125,9 +29116,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.2.4",
|
"version": "4.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
||||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="
|
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.28",
|
"version": "0.7.28",
|
||||||
|
|
|
@ -77,7 +77,6 @@
|
||||||
"react-sortable-tree": "^2.6.2",
|
"react-sortable-tree": "^2.6.2",
|
||||||
"semver-compare": "^1.0.0",
|
"semver-compare": "^1.0.0",
|
||||||
"slugify": "^1.4.6",
|
"slugify": "^1.4.6",
|
||||||
"typescript": "^4.2.3",
|
|
||||||
"url-join": "^4.0.1",
|
"url-join": "^4.0.1",
|
||||||
"use-react-router": "^1.0.7"
|
"use-react-router": "^1.0.7"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +177,7 @@
|
||||||
"start-server-and-test": "^1.11.0",
|
"start-server-and-test": "^1.11.0",
|
||||||
"ts-jest": "^24.2.0",
|
"ts-jest": "^24.2.0",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
||||||
|
"typescript": "^4.3.5",
|
||||||
"webpack": "^4.35.3",
|
"webpack": "^4.35.3",
|
||||||
"webpack-bundle-analyzer": "^4.4.1",
|
"webpack-bundle-analyzer": "^4.4.1",
|
||||||
"webpack-cli": "^3.3.6",
|
"webpack-cli": "^3.3.6",
|
||||||
|
@ -194,6 +194,7 @@
|
||||||
"fsevents": "^1.2.9"
|
"fsevents": "^1.2.9"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
"resetMocks": false,
|
||||||
"setupFiles": [
|
"setupFiles": [
|
||||||
"jest-localstorage-mock"
|
"jest-localstorage-mock"
|
||||||
],
|
],
|
||||||
|
|
|
@ -53,10 +53,9 @@ interface AppsListProps {
|
||||||
|
|
||||||
export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
const { action } = params;
|
const { action } = params;
|
||||||
const [activeInstallations, setActiveInstallations] = useLocalStorage(
|
const [activeInstallations, setActiveInstallations] = useLocalStorage<
|
||||||
"activeInstallations",
|
Array<Record<"id" | "name", string>>
|
||||||
[]
|
>("activeInstallations", []);
|
||||||
);
|
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { DEMO_MODE } from "@saleor/config";
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
import { User } from "@saleor/fragments/types/User";
|
import { User } from "@saleor/fragments/types/User";
|
||||||
import { SetLocalStorage } from "@saleor/hooks/useLocalStorage";
|
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getFullName, getMutationStatus } from "@saleor/misc";
|
import { getFullName, getMutationStatus } from "@saleor/misc";
|
||||||
import errorTracker from "@saleor/services/errorTracking";
|
import errorTracker from "@saleor/services/errorTracking";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
||||||
import { useMutation } from "react-apollo";
|
import { useMutation } from "react-apollo";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -57,7 +56,7 @@ export interface UseExternalAuthProvider extends UseAuthProvider {
|
||||||
) => Promise<ExternalObtainAccessTokens_externalObtainAccessTokens>;
|
) => Promise<ExternalObtainAccessTokens_externalObtainAccessTokens>;
|
||||||
}
|
}
|
||||||
export interface UseExternalAuthProviderOpts extends UseAuthProviderOpts {
|
export interface UseExternalAuthProviderOpts extends UseAuthProviderOpts {
|
||||||
setAuthPlugin: SetLocalStorage<any>;
|
setAuthPlugin: Dispatch<SetStateAction<any>>;
|
||||||
authPlugin: string;
|
authPlugin: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { DEMO_MODE } from "@saleor/config";
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
import { User } from "@saleor/fragments/types/User";
|
import { User } from "@saleor/fragments/types/User";
|
||||||
import { SetLocalStorage } from "@saleor/hooks/useLocalStorage";
|
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getFullName, getMutationStatus } from "@saleor/misc";
|
import { getFullName, getMutationStatus } from "@saleor/misc";
|
||||||
import errorTracker from "@saleor/services/errorTracking";
|
import errorTracker from "@saleor/services/errorTracking";
|
||||||
|
@ -9,7 +8,7 @@ import {
|
||||||
login as loginWithCredentialsManagementAPI,
|
login as loginWithCredentialsManagementAPI,
|
||||||
saveCredentials
|
saveCredentials
|
||||||
} from "@saleor/utils/credentialsManagement";
|
} from "@saleor/utils/credentialsManagement";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
||||||
import { useMutation } from "react-apollo";
|
import { useMutation } from "react-apollo";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -38,7 +37,7 @@ export interface UseSaleorAuthProvider extends UseAuthProvider {
|
||||||
loginByToken: (auth: string, csrf: string, user: User) => void;
|
loginByToken: (auth: string, csrf: string, user: User) => void;
|
||||||
}
|
}
|
||||||
export interface UseSaleorAuthProviderOpts extends UseAuthProviderOpts {
|
export interface UseSaleorAuthProviderOpts extends UseAuthProviderOpts {
|
||||||
setAuthPlugin: SetLocalStorage<any>;
|
setAuthPlugin: Dispatch<SetStateAction<any>>;
|
||||||
authPlugin: string;
|
authPlugin: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
80
src/hooks/useListSettings.test.ts
Normal file
80
src/hooks/useListSettings.test.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { defaultListSettings } from "@saleor/config";
|
||||||
|
import { ListViews } from "@saleor/types";
|
||||||
|
import { renderHook } from "@testing-library/react-hooks";
|
||||||
|
|
||||||
|
import useListSettings, { listSettingsStorageKey } from "./useListSettings";
|
||||||
|
|
||||||
|
const key = ListViews.CATEGORY_LIST;
|
||||||
|
const storedValue = {
|
||||||
|
...defaultListSettings,
|
||||||
|
[key]: {
|
||||||
|
...defaultListSettings[key],
|
||||||
|
rowNumber: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const valueWithoutKey = {
|
||||||
|
...defaultListSettings,
|
||||||
|
[key]: undefined
|
||||||
|
};
|
||||||
|
const valueWithoutSettings = {
|
||||||
|
...defaultListSettings,
|
||||||
|
[key]: {
|
||||||
|
foo: "bar"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useListSettings", () => {
|
||||||
|
it("properly inits from value", () => {
|
||||||
|
expect(localStorage.getItem(listSettingsStorageKey)).toBe(null);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useListSettings(key));
|
||||||
|
|
||||||
|
expect(result.current.settings).toStrictEqual(defaultListSettings[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits init if value is present", () => {
|
||||||
|
localStorage.setItem(listSettingsStorageKey, JSON.stringify(storedValue));
|
||||||
|
expect(localStorage.getItem(listSettingsStorageKey)).toBe(
|
||||||
|
JSON.stringify(storedValue)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useListSettings(key));
|
||||||
|
|
||||||
|
expect(result.current.settings).toStrictEqual(storedValue[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly merges new default values to saved ones", () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
listSettingsStorageKey,
|
||||||
|
JSON.stringify(valueWithoutKey)
|
||||||
|
);
|
||||||
|
expect(localStorage.getItem(listSettingsStorageKey)).toBe(
|
||||||
|
JSON.stringify(valueWithoutKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useListSettings(key));
|
||||||
|
|
||||||
|
expect(result.current.settings).toStrictEqual(defaultListSettings[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly fills missing settings", () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
listSettingsStorageKey,
|
||||||
|
JSON.stringify(valueWithoutSettings)
|
||||||
|
);
|
||||||
|
expect(localStorage.getItem(listSettingsStorageKey)).toBe(
|
||||||
|
JSON.stringify(valueWithoutSettings)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useListSettings(key));
|
||||||
|
|
||||||
|
expect(result.current.settings).toStrictEqual({
|
||||||
|
...valueWithoutSettings[key],
|
||||||
|
...defaultListSettings[key]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,9 +1,10 @@
|
||||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||||
import { useEffect } from "react";
|
import merge from "lodash/merge";
|
||||||
|
|
||||||
import { AppListViewSettings, defaultListSettings } from "./../config";
|
import { AppListViewSettings, defaultListSettings } from "./../config";
|
||||||
import { ListSettings, ListViews } from "./../types";
|
import { ListSettings, ListViews } from "./../types";
|
||||||
|
|
||||||
|
export const listSettingsStorageKey = "listConfig";
|
||||||
export interface UseListSettings<TColumns extends string = string> {
|
export interface UseListSettings<TColumns extends string = string> {
|
||||||
settings: ListSettings<TColumns>;
|
settings: ListSettings<TColumns>;
|
||||||
updateListSettings: <T extends keyof ListSettings<TColumns>>(
|
updateListSettings: <T extends keyof ListSettings<TColumns>>(
|
||||||
|
@ -15,18 +16,15 @@ export default function useListSettings<TColumns extends string = string>(
|
||||||
listName: ListViews
|
listName: ListViews
|
||||||
): UseListSettings<TColumns> {
|
): UseListSettings<TColumns> {
|
||||||
const [settings, setListSettings] = useLocalStorage<AppListViewSettings>(
|
const [settings, setListSettings] = useLocalStorage<AppListViewSettings>(
|
||||||
"listConfig",
|
listSettingsStorageKey,
|
||||||
defaultListSettings
|
storedListSettings => {
|
||||||
);
|
if (typeof storedListSettings !== "object") {
|
||||||
|
return defaultListSettings;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
return merge({}, defaultListSettings, storedListSettings);
|
||||||
if (settings[listName] === undefined) {
|
|
||||||
setListSettings(settings => ({
|
|
||||||
...settings,
|
|
||||||
[listName]: defaultListSettings[listName]
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}, []);
|
);
|
||||||
|
|
||||||
const updateListSettings = <T extends keyof ListSettings>(
|
const updateListSettings = <T extends keyof ListSettings>(
|
||||||
key: T,
|
key: T,
|
||||||
|
|
105
src/hooks/useLocalStorage.test.ts
Normal file
105
src/hooks/useLocalStorage.test.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { renderHook } from "@testing-library/react-hooks";
|
||||||
|
|
||||||
|
import useLocalStorage from "./useLocalStorage";
|
||||||
|
|
||||||
|
const key = "exampleKey";
|
||||||
|
const initialValue = "exampleValue";
|
||||||
|
const savedValue = "savedValue";
|
||||||
|
const numberValue = 12;
|
||||||
|
const objectValue = { foo: numberValue };
|
||||||
|
const booleanValue = true;
|
||||||
|
const postfix = "-test";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useLocalStorage", () => {
|
||||||
|
it("properly inits from value", () => {
|
||||||
|
expect(localStorage.getItem(key)).toBe(null);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
||||||
|
|
||||||
|
expect(result.current[0]).toBe(initialValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits initializing if value is found", () => {
|
||||||
|
localStorage.setItem(key, savedValue);
|
||||||
|
expect(localStorage.getItem(key)).toBe(savedValue);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
||||||
|
|
||||||
|
expect(result.current[0]).toBe(savedValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly casts value to number", () => {
|
||||||
|
localStorage.setItem(key, JSON.stringify(numberValue));
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
||||||
|
|
||||||
|
expect(result.current[0]).toBe(numberValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly casts value to boolean", () => {
|
||||||
|
localStorage.setItem(key, JSON.stringify(booleanValue));
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
||||||
|
|
||||||
|
expect(result.current[0]).toBe(booleanValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly casts value to object", () => {
|
||||||
|
localStorage.setItem(key, JSON.stringify(objectValue));
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
||||||
|
|
||||||
|
expect(result.current[0]).toStrictEqual(objectValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly inits from callback if value is not found", () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage(key, storedValue =>
|
||||||
|
storedValue ? storedValue + postfix : initialValue
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current[0]).toBe(initialValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly inits from callback if value is found", () => {
|
||||||
|
localStorage.setItem(key, savedValue);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage(key, storedValue =>
|
||||||
|
storedValue ? storedValue + postfix : initialValue
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current[0]).toBe(savedValue + postfix);
|
||||||
|
expect(localStorage.getItem(key)).toBe(savedValue + postfix);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly inits from callback if value is object", () => {
|
||||||
|
localStorage.setItem(key, JSON.stringify(objectValue));
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useLocalStorage(key, storedValue => {
|
||||||
|
if (typeof storedValue === "object") {
|
||||||
|
return {
|
||||||
|
...storedValue,
|
||||||
|
bar: "baz"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectValue;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const newValue = {
|
||||||
|
foo: numberValue,
|
||||||
|
bar: "baz"
|
||||||
|
};
|
||||||
|
expect(result.current[0]).toStrictEqual(newValue);
|
||||||
|
expect(localStorage.getItem(key)).toBe(JSON.stringify(newValue));
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,33 +1,62 @@
|
||||||
import { useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
|
||||||
export type SetLocalStorageValue<T> = T | ((prevValue: T) => T);
|
export type UseLocalStorage<T> = [T, Dispatch<SetStateAction<T>>];
|
||||||
export type SetLocalStorage<T> = (value: SetLocalStorageValue<T>) => void;
|
|
||||||
export default function useLocalStorage<T>(
|
export default function useLocalStorage<T>(
|
||||||
key: string,
|
key: string,
|
||||||
initialValue: T
|
initialValue: SetStateAction<T>
|
||||||
): [T, SetLocalStorage<T>] {
|
): UseLocalStorage<T> {
|
||||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
const saveToLocalStorage = (valueToStore: T) => {
|
||||||
let result: T;
|
|
||||||
try {
|
try {
|
||||||
const item = window.localStorage.getItem(key);
|
if (typeof valueToStore === "string") {
|
||||||
result = item ? JSON.parse(item) : initialValue;
|
localStorage.setItem(key, valueToStore);
|
||||||
} catch {
|
} else {
|
||||||
result = initialValue;
|
localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
const setValue = (value: SetLocalStorageValue<T>) => {
|
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
||||||
setStoredValue(valueToStore);
|
|
||||||
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
||||||
} catch {
|
} catch {
|
||||||
console.warn(`Could not save ${key} to localStorage`);
|
console.warn(`Could not save ${key} to localStorage`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getValue(value: T, initOrCb: SetStateAction<T>): T {
|
||||||
|
if (initOrCb instanceof Function) {
|
||||||
|
const newValue = initOrCb(value);
|
||||||
|
saveToLocalStorage(newValue);
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value ?? initOrCb;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||||
|
let result: T | null;
|
||||||
|
const item = localStorage.getItem(key);
|
||||||
|
|
||||||
|
if (item === null) {
|
||||||
|
return getValue(null, initialValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(item);
|
||||||
|
if (!parsed) {
|
||||||
|
throw new Error("Empty value");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = parsed;
|
||||||
|
} catch {
|
||||||
|
// Casting to T (which should resolve to string) because JSON.parse would
|
||||||
|
// throw an error if "foo" was passed, but properly casting "true" or "1"
|
||||||
|
// to their respective types
|
||||||
|
result = (item as unknown) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getValue(result, initialValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = (value: SetStateAction<T>) => {
|
||||||
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||||
|
setStoredValue(valueToStore);
|
||||||
|
saveToLocalStorage(valueToStore);
|
||||||
|
};
|
||||||
|
|
||||||
return [storedValue, setValue];
|
return [storedValue, setValue];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue