This commit is contained in:
mario
2025-03-07 13:47:44 +07:00
commit c4efec5a14
3358 changed files with 303774 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
import useResizeObserver from './useResizeObserver';
import useSessionStorage from './useSessionStorage';
export { useResizeObserver, useSessionStorage };

View 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;

View 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);
});
});

View 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;