As a NextJS developer, you might be encountering issues involving the window object every single time.
To resolve this, the solutions that you may have come up with must be these two:
if (typeof window !== 'undefined') {
// localStorage code here
}
if (process.browser) {
// localStorage code here
}
However, process.browser was deprecated last January 2020. So the first one would be the ideal solution and the recommended approach. click here for more info.
Creating the hook
Creating a react hook to handle local/session storage may come in handy since typing the first solution may be redundant if ever we are accessing local/session storage multiple times.
Lets create a file named useStorage.ts, You can name it whatever you want. But first, we need to do the typings first.
{/*
`Storage` User will determine what storage object will he/she be using.
This way, user cant pass unnecessary string values
*/}
type StorageType = 'session' | 'local';
{/*
`UseStorageReturnValue` This is just a return value type for our hook.
We can add additional typings later.
*/}
type UseStorageReturnValue = {
getItem: (key: string, type?: StorageType) => string;
};
Then, lets create the hook.
const useStorage = (): UseStorageReturnValue => {
const isBrowser: boolean = ((): boolean => typeof window !== 'undefined')();
const getItem = (key: string, type?: StorageType): string => {
const storageType: 'localStorage' | 'sessionStorage' = `${type ?? 'session'}Storage`;
return isBrowser ? window[storageType][key] : '';
};
return {
getItem,
};
};
export default useStorage;
isBrowser - This is a variable that is initialized with a function that is immediately invoked. For more info about Immediately Invoked Function Expressions (IIFE), check here. This variable checks if the user is on the client or server
getItem - Function that accepts two parameters. It just returns the value from localStorage and empty string if its undefined.
Lets make use of the hook now
First, lets import the hook.
import useStorage from 'hooks/useStorage';
Call the hook and destructure the getItem function from it.
const { getItem } = useStorage();
const token = getItem('token');
console.log(token); // will return either a <token-value> or <''>
Thats it! Now, we can add more functionality like, setting a storage value, or deleting, and so on.
Adding a setItem method
Adding additional methods may require an application to be refactored or to reuse functions as much as possible. Since we are adding the setItem method, we need to add typings accordingly.
type UseStorageReturnValue = {
getItem: (key: string, type?: StorageType) => string;
// you can set the return value to void or anything, as for my side, i just want to
// check if the value was saved or not
setItem: (key: string, value: string, type?: StorageType) => boolean;
};
Lets refactor the getItem
code and reuse the storageType
variable inside. Here, we use it as a function.
const useStorage = (): UseStorageReturnValue => {
const storageType = (type?: StorageType): 'localStorage' | 'sessionStorage' => </span><span class="p">${</span><span class="kd">type</span> <span class="o">??</span> <span class="dl">'</span><span class="s1">session</span><span class="dl">'</span><span class="p">}</span><span class="s2">Storage
;
const isBrowser: boolean = ((): boolean => typeof window !== 'undefined')();
const getItem = (key: string, type?: StorageType): string => {
return isBrowser ? window[storageType(type)][key] : '';
};
const setItem = (key: string, value: string, type?: StorageType): boolean => {
if (isBrowser) {
window[storageType(type)].setItem(key, value);
return true;
}
return false;
};
Adding a removeItem function
Similar approach to the one listed above, lets just add this to the UseStorageReturnValue
// We'll set this to a return type value of void since
// running the method always throws undefined
removeItem: (key: string, type?: StorageType) => void;
Final Code
type StorageType = 'session' | 'local';
type UseStorageReturnValue = {
getItem: (key: string, type?: StorageType) => string;
setItem: (key: string, value: string, type?: StorageType) => boolean;
removeItem: (key: string, type?: StorageType) => void;
};
const useStorage = (): UseStorageReturnValue => {
const storageType = (type?: StorageType): 'localStorage' | 'sessionStorage' => </span><span class="p">${</span><span class="kd">type</span> <span class="o">??</span> <span class="dl">'</span><span class="s1">session</span><span class="dl">'</span><span class="p">}</span><span class="s2">Storage
;
const isBrowser: boolean = ((): boolean => typeof window !== 'undefined')();
const getItem = (key: string, type?: StorageType): string => {
return isBrowser ? window[storageType(type)][key] : '';
};
const setItem = (key: string, value: string, type?: StorageType): boolean => {
if (isBrowser) {
window[storageType(type)].setItem(key, value);
return true;
}
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
};
const removeItem = (key: string, type?: StorageType): void => {
window[storageType(type)].removeItem(key);
};
return {
getItem,
setItem,
removeItem,
};
};
export default useStorage;
Thoughts?
This code can be refactored and methods can also be extended but I'm gonna leave it that way for now. If you have any suggestions, please feel free to comment. Thanks for reading guys.
Top comments (9)
Thoughts?
Fuck react.
Thanks!!
I don't see this function using any hooks?
Functions are hooks if react native hooks are invoked during their execution. Such as
useState
,useRef
,useEffect
... otherwise they are just functions.If I understand correctly examples
useStorage
is actually not a hook, but just a normal function.This is a custom hook written by the author.
From the documentation by react, there is example that didn't use
useState
,useRef
,useEffect
. Find keyword useFormInput. I don't think it is necessary to use built-in hook inside a custom hook.react.dev/learn/reusing-logic-with...
The purpose of a custom hook is to abstract away repetitive or complex logic into a reusable function that can be used across multiple components.
Thanks for the comment, it made me dive in deeper to understand better. Great article!
Thank you so much sir! This is a great hook!
Granted that I'm a react noob, but I don't understand what the point is. Why create a storage that behaves unpredictably? If my code happens to run on the server, I want it to fail hard if I'm trying to access storage that doesn't exist there. This will just pass the issue farther down the line and cause strange failures that might be hard to find. What am I missing?
Awesome article man!
ykwim
I thought you will use premade hooks that react provides! but you are using custom hook