Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sessionStorage and localStorage should have similar behavior, their only difference is where the data is being stored and it's persistence which should be abstracted for this library. #979

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/useSessionStorage.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const Demo = () => {
<div>Value: {value}</div>
<button onClick={() => setValue('bar')}>bar</button>
<button onClick={() => setValue('baz')}>baz</button>
<button onClick={() => remove()}>Remove</button>
</div>
);
};
Expand All @@ -24,12 +25,20 @@ const Demo = () => {

## Reference


```js
useSessionStorage(key);
useSessionStorage(key, initialValue);
useSessionStorage(key, initialValue, raw);
useSessionStorage(key, initialValue, { raw: true });
useSessionStorage(key, initialValue, {
raw: false,
serializer: (value: T) => string,
deserializer: (value: string) => T,
});
```

- `key` &mdash; `sessionStorage` key to manage.
- `initialValue` &mdash; initial value to set, if value in `sessionStorage` is empty.
- `raw` &mdash; boolean, if set to `true`, hook will not attempt to JSON serialize stored values.
- `serializer` &mdash; custom serializer (defaults to `JSON.stringify`)
- `deserializer` &mdash; custom deserializer (defaults to `JSON.parse`)
81 changes: 3 additions & 78 deletions src/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,13 @@
/* eslint-disable */
import { useState, useCallback, Dispatch, SetStateAction } from 'react';
import { isClient } from './util';

type parserOptions<T> =
| {
raw: true;
}
| {
raw: false;
serializer: (value: T) => string;
deserializer: (value: string) => T;
};

const noop = () => {};
import { Dispatch, SetStateAction } from 'react';
import useStorage, { parserOptions } from './useStorage';

const useLocalStorage = <T>(
key: string,
initialValue?: T,
options?: parserOptions<T>
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {
if (!isClient) {
return [initialValue as T, noop, noop];
}
if (!key) {
throw new Error('useLocalStorage key may not be falsy');
}

const deserializer = options ? (options.raw ? value => value : options.deserializer) : JSON.parse;

const [state, setState] = useState<T | undefined>(() => {
try {
const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify;

const localStorageValue = localStorage.getItem(key);
if (localStorageValue !== null) {
return deserializer(localStorageValue);
} else {
initialValue && localStorage.setItem(key, serializer(initialValue));
return initialValue;
}
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw. JSON.parse and JSON.stringify
// can throw, too.
return initialValue;
}
});

const set: Dispatch<SetStateAction<T | undefined>> = useCallback(
valOrFunc => {
try {
const newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc;
if (typeof newState === 'undefined') return;
let value: string;

if (options)
if (options.raw)
if (typeof newState === 'string') value = newState;
else value = JSON.stringify(newState);
else if (options.serializer) value = options.serializer(newState);
else value = JSON.stringify(newState);
else value = JSON.stringify(newState);

localStorage.setItem(key, value);
setState(deserializer(value));
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw. Also JSON.stringify can throw.
}
},
[key, setState]
);

const remove = useCallback(() => {
try {
localStorage.removeItem(key);
setState(undefined);
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw.
}
}, [key, setState]);

return [state, set, remove];
return useStorage('localStorage', key, initialValue, options);
};

export default useLocalStorage;
45 changes: 9 additions & 36 deletions src/useSessionStorage.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,13 @@
/* eslint-disable */
import { useEffect, useState } from 'react';
import { isClient } from './util';

const useSessionStorage = <T>(key: string, initialValue?: T, raw?: boolean): [T, (value: T) => void] => {
if (!isClient) {
return [initialValue as T, () => {}];
}

const [state, setState] = useState<T>(() => {
try {
const sessionStorageValue = sessionStorage.getItem(key);
if (typeof sessionStorageValue !== 'string') {
sessionStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue));
return initialValue;
} else {
return raw ? sessionStorageValue : JSON.parse(sessionStorageValue || 'null');
}
} catch {
// If user is in private mode or has storage restriction
// sessionStorage can throw. JSON.parse and JSON.stringify
// cat throw, too.
return initialValue;
}
});

useEffect(() => {
try {
const serializedState = raw ? String(state) : JSON.stringify(state);
sessionStorage.setItem(key, serializedState);
} catch {
// If user is in private mode or has storage restriction
// sessionStorage can throw. Also JSON.stringify can throw.
}
});

return [state, setState];
import { Dispatch, SetStateAction } from 'react';
import useStorage, { parserOptions } from './useStorage';

const useSessionStorage = <T>(
key: string,
initialValue?: T,
options?: parserOptions<T>
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {
return useStorage('sessionStorage', key, initialValue, options);
};

export default useSessionStorage;
96 changes: 96 additions & 0 deletions src/useStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* eslint-disable */
import { useState, useCallback, Dispatch, SetStateAction, useMemo } from 'react';
import { isClient } from './util';

export type parserOptions<T> =
| {
raw: true;
}
| {
raw: false;
serializer: (value: T) => string;
deserializer: (value: string) => T;
};

const noop = () => {};

const useStorage = <T>(
storageType: string,
key: string,
initialValue?: T,
options?: parserOptions<T>
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {
if (!isClient) {
return [initialValue as T, noop, noop];
}
if (!key) {
throw new Error('Storage key may not be falsy');
}
//default to localStorage

let storage = useMemo(() => {
if (storageType == 'sessionStorage') return sessionStorage;
//defaults to localStorage what ever the key was used
else return localStorage;
}, [storageType]);

const deserializer = options ? (options.raw ? value => value : options.deserializer) : JSON.parse;

const [state, setState] = useState<T | undefined>(() => {
try {
const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify;

const storageValue = storage.getItem(key);
if (storageValue !== null) {
return deserializer(storageValue);
} else {
initialValue && storage.setItem(key, serializer(initialValue));
return initialValue;
}
} catch {
// If user is in private mode or has storage restriction
// storage can throw. JSON.parse and JSON.stringify
// can throw, too.
return initialValue;
}
});

const set: Dispatch<SetStateAction<T | undefined>> = useCallback(
valOrFunc => {
try {
const newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc;
if (typeof newState === 'undefined') return;
let value: string;

if (options)
if (options.raw)
if (typeof newState === 'string') value = newState;
else value = JSON.stringify(newState);
else if (options.serializer) value = options.serializer(newState);
else value = JSON.stringify(newState);
else value = JSON.stringify(newState);

storage.setItem(key, value);
setState(deserializer(value));
} catch {
// If user is in private mode or has storage restriction
// storage can throw. Also JSON.stringify can throw.
}
},
[key, setState]
);

const remove = useCallback(() => {
try {
storage.removeItem(key);
setState(undefined);
} catch {
// If user is in private mode or has storage restriction
// storage can throw.
}
}, [key, setState]);

return [state, set, remove];
};

export default useStorage;
3 changes: 2 additions & 1 deletion stories/useSessionStorage.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useSessionStorage } from '../src';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const [value, setValue] = useSessionStorage('hello-key', 'foo');
const [value, setValue, remove] = useSessionStorage('hello-key', 'foo');

return (
<div>
<div>Value: {value}</div>
<button onClick={() => setValue('bar')}>bar</button>
<button onClick={() => setValue('baz')}>baz</button>
<button onClick={() => remove()}>Remove</button>
</div>
);
};
Expand Down
Loading