mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 23:15:43 +01:00
Simplify local storage access
The caching for local storage seems way over-engineered. With this the context for the local storage is removed and the local storage is accessed directly. Pushed-by: k8s-git-ops<admin@cloudogu.com> Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com> Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
64
scm-ui/ui-api/src/localStorage.ts
Normal file
64
scm-ui/ui-api/src/localStorage.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
type LocalStorageSetter<T> = (value: T | ((previousValue: T) => T)) => void;
|
||||
|
||||
const determineInitialValue = <T>(key: string, initialValue: T) => {
|
||||
try {
|
||||
const itemFromStorage = localStorage.getItem(key);
|
||||
return itemFromStorage ? JSON.parse(itemFromStorage) : initialValue;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return initialValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides an api to access the browser's local storage for a given key.
|
||||
*
|
||||
* @param key The local storage key
|
||||
* @param initialValue Value to be used if the local storage does not yet have the given key defined
|
||||
*/
|
||||
export function useLocalStorage<T>(key: string, initialValue: T): [value: T, setValue: LocalStorageSetter<T>] {
|
||||
const initialValueOrValueFromStorage = useMemo(() => determineInitialValue(key, initialValue), [key, initialValue]);
|
||||
const [item, setItem] = useState(initialValueOrValueFromStorage);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: StorageEvent) => {
|
||||
if (event.key === key) {
|
||||
setItem(determineInitialValue(key, initialValue));
|
||||
}
|
||||
};
|
||||
window.addEventListener("storage", listener);
|
||||
return () => window.removeEventListener("storage", listener);
|
||||
}, [key, initialValue]);
|
||||
|
||||
const setValue: LocalStorageSetter<T> = (newValue) => {
|
||||
const computedNewValue = newValue instanceof Function ? newValue(item) : newValue;
|
||||
setItem(computedNewValue);
|
||||
const json = JSON.stringify(computedNewValue);
|
||||
localStorage.setItem(key, json);
|
||||
// storage event is no triggered in same tab
|
||||
window.dispatchEvent(
|
||||
new StorageEvent("storage", { key, oldValue: item, newValue: json, storageArea: localStorage })
|
||||
);
|
||||
};
|
||||
|
||||
return [item, setValue];
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
type LocalStorage = {
|
||||
getItem: <T>(key: string, fallback: T) => T;
|
||||
setItem: <T>(key: string, value: T) => void;
|
||||
preload: <T>(key: string, initialValue: T) => void;
|
||||
};
|
||||
|
||||
const LocalStorageContext = createContext<LocalStorage>(null as unknown as LocalStorage);
|
||||
|
||||
/**
|
||||
* Cache provider for local storage which enables listening to changes and triggering re-renders when writing.
|
||||
*
|
||||
* Only required once as a wrapper for the whole application.
|
||||
*
|
||||
* @see useLocalStorage
|
||||
*/
|
||||
export const LocalStorageProvider: FC = ({ children }) => {
|
||||
const [localStorageCache, setLocalStorageCache] = useState<Record<string, unknown>>({});
|
||||
|
||||
const setItem = useCallback(<T,>(key: string, value: T) => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
setLocalStorageCache((prevState) => ({
|
||||
...prevState,
|
||||
[key]: value,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const getItem = useCallback(
|
||||
<T,>(key: string, fallback: T): T => (key in localStorageCache ? (localStorageCache[key] as T) : fallback),
|
||||
[localStorageCache]
|
||||
);
|
||||
|
||||
const preload = useCallback(
|
||||
<T,>(key: string, initialValue: T) => {
|
||||
if (!(key in localStorageCache)) {
|
||||
let initialLoadResult: T | undefined;
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
initialLoadResult = item ? JSON.parse(item) : initialValue;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
initialLoadResult = initialValue;
|
||||
}
|
||||
setItem(key, initialLoadResult);
|
||||
}
|
||||
},
|
||||
[localStorageCache, setItem]
|
||||
);
|
||||
|
||||
return (
|
||||
<LocalStorageContext.Provider value={useMemo(() => ({ getItem, setItem, preload }), [getItem, preload, setItem])}>
|
||||
{children}
|
||||
</LocalStorageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides an api to access the browser's local storage for a given key.
|
||||
*
|
||||
* @param key The local storage key
|
||||
* @param initialValue Value to be used if the local storage does not yet have the given key defined
|
||||
*/
|
||||
export function useLocalStorage<T>(
|
||||
key: string,
|
||||
initialValue: T
|
||||
): [value: T, setValue: (value: T | ((previousConfig: T) => T)) => void] {
|
||||
const { getItem, setItem, preload } = useContext(LocalStorageContext);
|
||||
const value = useMemo(() => getItem(key, initialValue), [getItem, initialValue, key]);
|
||||
const setValue = useCallback(
|
||||
(newValue: T | ((previousConfig: T) => T)) =>
|
||||
// @ts-ignore T could be a function type, although this does not make sense because function types are not serializable to json
|
||||
setItem(key, typeof newValue === "function" ? newValue(value) : newValue),
|
||||
// eslint does not understand generics in certain circumstances
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[key, setItem, value]
|
||||
);
|
||||
useEffect(() => preload(key, initialValue), [initialValue, key, preload]);
|
||||
return useMemo(() => [value, setValue], [setValue, value]);
|
||||
}
|
||||
Reference in New Issue
Block a user