Unverified Commit 7c9a9204 authored by Guilherme Gazzo's avatar Guilherme Gazzo Committed by GitHub
Browse files

feat: Hooks useLocalStorage and useSessionStorage (#248)



* useLocalStorage and useSessionStorage

* Unit tests and renamed file

* Update useStorage.ts

* Remove browser environment check in useStorage effect

* Specify setter type returned by useStorage hooks

* Add tests for server

* Remove FakeStorage; let errors free to be thrown
Co-authored-by: default avatarTasso Evangelista <tasso.evangelista@rocket.chat>
parent 09f95edd
......@@ -57,8 +57,12 @@ yarn test
- [Comparator](#comparator)
- [useStableArray](#usestablearray)
- [Parameters](#parameters-13)
- [useToggle](#usetoggle)
- [useLocalStorage](#uselocalstorage)
- [Parameters](#parameters-14)
- [useSessionStorage](#usesessionstorage)
- [Parameters](#parameters-15)
- [useToggle](#usetoggle)
- [Parameters](#parameters-16)
- [useUniqueId](#useuniqueid)
### useAutoFocus
......@@ -231,6 +235,65 @@ Hook to create an array with stable identity if its elements are equal.
Returns **T** the passed array if the elements are NOT equals; the previously
stored array otherwise
### useLocalStorage
Hook to deal with localStorage
#### Parameters
- `key`
- `initialValue`
Returns **any** a state and a setter function
### useSessionStorage
Hook to deal with sessionStorage
#### Parameters
- `key`
- `initialValue`
Returns **any** a state and a setter function
### useToggle
Hook to create a toggleable boolean state.
#### Parameters
- `initialValue` **([boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) | function (): [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean))?** the initial value or the initial state generator function
Returns **\[[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean), D]** a state boolean value and a state toggler function
### useUniqueId
Hook to keep a unique ID string.
Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the unique ID string
# Returns **any** a state and a setter function
### Comparator
Type: function (a: T, b: T): [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)
### useStableArray
Hook to create an array with stable identity if its elements are equal.
#### Parameters
- `array` **T** the array
- `compare` **[Comparator](#comparator)** the equality function that checks if two array elements are
equal (optional, default `Object.is`)
Returns **T** the passed array if the elements are NOT equals; the previously
stored array otherwise
> > > > > > > 09f95edd7f296de45bc62caa16a1f43848fe3027
### useToggle
Hook to create a toggleable boolean state.
......
......@@ -14,5 +14,6 @@ export * from './useMutableCallback';
export * from './useResizeObserver';
export * from './useSafely';
export * from './useStableArray';
export * from './useStorage';
export * from './useToggle';
export * from './useUniqueId';
/**
* @jest-environment node
*/
import { createElement, FunctionComponent, StrictMode } from 'react';
import { renderToString } from 'react-dom/server';
import { useLocalStorage, useSessionStorage } from './useStorage';
describe('useLocalStorage hook', () => {
it('returns a default value', () => {
let value: string;
const TestComponent: FunctionComponent = () => {
[value] = useLocalStorage('value-key', 'value-default');
return null;
};
renderToString(
createElement(StrictMode, {}, createElement(TestComponent)),
);
expect(value).toStrictEqual('value-default');
});
});
describe('useSessionStorage hook', () => {
it('returns a default value', () => {
let value: string;
const TestComponent: FunctionComponent = () => {
[value] = useSessionStorage('value-key', 'value-default');
return null;
};
renderToString(
createElement(StrictMode, {}, createElement(TestComponent)),
);
expect(value).toStrictEqual('value-default');
});
});
import { createElement, FunctionComponent, StrictMode, Dispatch, SetStateAction } from 'react';
import { render } from 'react-dom';
import { act } from 'react-dom/test-utils';
import { useLocalStorage, useSessionStorage } from './useStorage';
describe('useLocalStorage hook', () => {
it('returns a default value', () => {
let value: string;
const TestComponent: FunctionComponent = () => {
[value] = useLocalStorage('value-key', 'value-default');
return null;
};
act(() => {
render(
createElement(StrictMode, {}, createElement(TestComponent)),
document.createElement('div'),
);
});
expect(value).toStrictEqual('value-default');
});
it('returns a new value', () => {
let value: string;
let setValue: Dispatch<SetStateAction<string>>;
const TestComponent: FunctionComponent = () => {
[value, setValue] = useLocalStorage('value-key', 'value-default');
return null;
};
act(() => {
render(
createElement(StrictMode, {}, createElement(TestComponent)),
document.createElement('div'),
);
});
act(() => {
setValue('value-new');
});
expect(value).toStrictEqual('value-new');
});
});
describe('useSessionStorage hook', () => {
it('returns a default value', () => {
let value: string;
const TestComponent: FunctionComponent = () => {
[value] = useSessionStorage('value-key', 'value-default');
return null;
};
act(() => {
render(
createElement(StrictMode, {}, createElement(TestComponent)),
document.createElement('div'),
);
});
expect(value).toStrictEqual('value-default');
});
it('returns a new value', () => {
let value: string;
let setValue: Dispatch<SetStateAction<string>>;
const TestComponent: FunctionComponent = () => {
[value, setValue] = useSessionStorage('value-key', 'value-default');
return null;
};
act(() => {
render(
createElement(StrictMode, {}, createElement(TestComponent)),
document.createElement('div'),
);
});
act(() => {
setValue('value-new');
});
expect(value).toStrictEqual('value-new');
});
});
import { useState, useEffect, useCallback, Dispatch, SetStateAction } from 'react';
const makeStorage = (
storageFactory: Storage | (() => Storage),
name: string,
): <T>(key: string, initialValue: T) => [T, Dispatch<SetStateAction<T>>] => {
let storage: Storage = null;
if (typeof window !== 'undefined') {
storage = typeof storageFactory === 'function' ? storageFactory() : storageFactory;
}
const getKey = (key: string): string => `fuselage-${ name }-${ key }`;
return function useGenericStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
const [storedValue, setStoredValue] = useState<T>(() => {
if (!storage) {
return initialValue;
}
const item = storage.getItem(getKey(key));
return item ? JSON.parse(item) : initialValue;
});
const setValue: Dispatch<SetStateAction<T>> = useCallback((value: T extends unknown ? SetStateAction<T> : never): void => {
setStoredValue((prevValue: T) => {
const valueToStore: T = typeof value === 'function' ? value(prevValue) : value;
storage.setItem(key, JSON.stringify(valueToStore));
return valueToStore;
});
}, [key]);
useEffect(() => {
const handleEvent = (event: StorageEvent): void => {
if (event.key !== getKey(key)) {
return;
}
setStoredValue(JSON.parse(event.newValue));
};
window.addEventListener('storage', handleEvent);
return () => {
window.removeEventListener('storage', handleEvent);
};
}, [key]);
return [storedValue, setValue];
};
};
/**
* Hook to deal with localStorage
* @param key
* @param initialValue
* @return a state and a setter function
*/
export const useLocalStorage = makeStorage(() => window.localStorage, 'localStorage');
/**
* Hook to deal with sessionStorage
* @param key
* @param initialValue
* @return a state and a setter function
*/
export const useSessionStorage = makeStorage(() => window.sessionStorage, 'sessionStorage');
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment