init
This commit is contained in:
4
platform/ui/src/hooks/index.ts
Normal file
4
platform/ui/src/hooks/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import useResizeObserver from './useResizeObserver';
|
||||
import useSessionStorage from './useSessionStorage';
|
||||
|
||||
export { useResizeObserver, useSessionStorage };
|
||||
25
platform/ui/src/hooks/useResizeObserver.ts
Normal file
25
platform/ui/src/hooks/useResizeObserver.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* A resizeObserver React hook that with useEffect attaches a ResizeObserver to the
|
||||
* given element such that the given callback is invoked whenever the element is resized.
|
||||
* <p>
|
||||
* Care is taken to disconnect the ResizeObserver whenever either the element or the callback change.
|
||||
*
|
||||
* @param elem the element to listen for resizing
|
||||
* @param callback the callback to invoke when the element is resized
|
||||
*/
|
||||
const useResizeObserver = (elem: HTMLElement, callback: ResizeObserverCallback): void => {
|
||||
useEffect(() => {
|
||||
if (!elem || !callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(callback);
|
||||
resizeObserver.observe(elem);
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, [elem, callback]);
|
||||
};
|
||||
|
||||
export default useResizeObserver;
|
||||
103
platform/ui/src/hooks/useSessionStorage.test.js
Normal file
103
platform/ui/src/hooks/useSessionStorage.test.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import useSessionStorage from './useSessionStorage';
|
||||
|
||||
const SESSION_STORAGE_KEY = 'test';
|
||||
|
||||
describe('Hook Session Storage', () => {
|
||||
beforeEach(() => {
|
||||
window.sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('hook should return state and setState', () => {
|
||||
const data = { test: 1 };
|
||||
const { result } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
expect(hookState).toStrictEqual(data);
|
||||
expect(typeof setHookState).toBe('function');
|
||||
});
|
||||
|
||||
it('hook should store data on sessionStorage', () => {
|
||||
const data = { test: 2 };
|
||||
renderHook(() => useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data }));
|
||||
|
||||
const dataStr = JSON.stringify(data);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
expect(dataSessionStorage).toEqual(dataStr);
|
||||
});
|
||||
|
||||
it('hook should return stored data from sessionStorage', () => {
|
||||
const data = { test: 3 };
|
||||
const dataToCompare = { test: 4 };
|
||||
|
||||
window.sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(dataToCompare));
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
expect(hookState).toStrictEqual(dataToCompare);
|
||||
});
|
||||
|
||||
it('hook should provide a setState method which updates its state', () => {
|
||||
const data = { test: 5 };
|
||||
const dataToCompare = { test: 6 };
|
||||
const { result } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
act(() => {
|
||||
setHookState(dataToCompare);
|
||||
});
|
||||
|
||||
const dataToCompareStr = JSON.stringify(dataToCompare);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
|
||||
const [hookStateToCompare] = result.current;
|
||||
expect(dataSessionStorage).toEqual(dataToCompareStr);
|
||||
expect(hookStateToCompare).toStrictEqual(dataToCompare);
|
||||
});
|
||||
|
||||
it('hook state must be preserved in case rerender', () => {
|
||||
const data = { test: 7 };
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
|
||||
rerender();
|
||||
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
const dataToCompareStr = JSON.stringify(data);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
|
||||
expect(dataSessionStorage).toEqual(dataToCompareStr);
|
||||
expect(hookState).toStrictEqual(data);
|
||||
});
|
||||
|
||||
it('hook state must be preserved in case multiple operations and rerender', () => {
|
||||
const data = { test: 8 };
|
||||
const dataToCompare = { test: 9 };
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
act(() => {
|
||||
setHookState(dataToCompare);
|
||||
});
|
||||
|
||||
rerender();
|
||||
|
||||
const dataToCompareStr = JSON.stringify(dataToCompare);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
|
||||
const [hookStateToCompare] = result.current;
|
||||
expect(dataSessionStorage).toEqual(dataToCompareStr);
|
||||
expect(hookStateToCompare).toStrictEqual(dataToCompare);
|
||||
});
|
||||
});
|
||||
71
platform/ui/src/hooks/useSessionStorage.tsx
Normal file
71
platform/ui/src/hooks/useSessionStorage.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* A map of session storage items that should be cleared out of session storage
|
||||
* when the page unloads.
|
||||
*/
|
||||
const sessionItemsToClearOnUnload: Map<string, string> = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* This callback simulates clearing the various session items when a page unloads.
|
||||
* When the page is hidden the session storage items are removed but maintained
|
||||
* in the map above in case the page becomes visible again. So those pages that
|
||||
* are hidden because they are being unloaded have their session storage disposed
|
||||
* of for ever. For those pages that are hidden, but later return to visible,
|
||||
* this callback restores the session storage from the map above.
|
||||
*/
|
||||
const visibilityChangeCallback = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
Array.from(sessionItemsToClearOnUnload.keys()).forEach(key => {
|
||||
window.sessionStorage.removeItem(key);
|
||||
});
|
||||
} else {
|
||||
Array.from(sessionItemsToClearOnUnload.keys()).forEach(key => {
|
||||
window.sessionStorage.setItem(key, sessionItemsToClearOnUnload.get(key));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Technically there is no memory leak here because the listener needs to
|
||||
* persist until the page unloads and once the page unloads it will be gone.
|
||||
*/
|
||||
document.addEventListener('visibilitychange', visibilityChangeCallback);
|
||||
|
||||
type useSessionStorageProps = {
|
||||
key: string;
|
||||
defaultValue: unknown;
|
||||
clearOnUnload: boolean;
|
||||
};
|
||||
|
||||
const useSessionStorage = ({
|
||||
key,
|
||||
defaultValue = {},
|
||||
clearOnUnload = false,
|
||||
}: useSessionStorageProps) => {
|
||||
const valueFromStorage = window.sessionStorage.getItem(key);
|
||||
const storageValue = valueFromStorage ? JSON.parse(valueFromStorage) : defaultValue;
|
||||
const [sessionItem, setSessionItem] = useState({ ...storageValue });
|
||||
|
||||
const updateSessionItem = useCallback(value => {
|
||||
setSessionItem({ ...value });
|
||||
|
||||
const valueAsStr = JSON.stringify(value);
|
||||
|
||||
if (!clearOnUnload || document.visibilityState === 'visible') {
|
||||
window.sessionStorage.setItem(key, valueAsStr);
|
||||
}
|
||||
|
||||
if (clearOnUnload) {
|
||||
sessionItemsToClearOnUnload.set(key, valueAsStr);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateSessionItem(sessionItem);
|
||||
}, []);
|
||||
|
||||
return [sessionItem, updateSessionItem];
|
||||
};
|
||||
|
||||
export default useSessionStorage;
|
||||
Reference in New Issue
Block a user