DEV Community

Ralph Largo
Ralph Largo

Posted on • Edited on

NextJS way for accessing local or session storage variables

As a NextJS developer, you might be encountering issues involving the window object every single time.

image

To resolve this, the solutions that you may have come up with must be these two:



if (typeof window !== 'undefined') {
 // localStorage code here
}


Enter fullscreen mode Exit fullscreen mode


if (process.browser) {
 // localStorage code here
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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 <''>


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode




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;

Enter fullscreen mode Exit fullscreen mode




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>
Enter fullscreen mode Exit fullscreen mode

};

const removeItem = (key: string, type?: StorageType): void => {
window[storageType(type)].removeItem(key);
};

return {
getItem,
setItem,
removeItem,
};
};

export default useStorage;

Enter fullscreen mode Exit fullscreen mode




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.

image

Top comments (9)

Collapse
 
eimkasp profile image
Eimantas

Thoughts?
Fuck react.

Collapse
 
maurciobuffa profile image
maurciobuffa

Thanks!!

Collapse
 
jenbuska profile image
jEnbuska

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.

Collapse
 
shianweipang profile image
Shian Wei Pang • Edited

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!

Collapse
 
johnfrades profile image
John Frades

Thank you so much sir! This is a great hook!

Collapse
 
prydin profile image
Pontus Rydin

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?

Collapse
 
harrysoer profile image
Harry Angelo Soer

Awesome article man!

Collapse
 
shimeyori profile image
〆ゟ

Image description
ykwim

Collapse
 
muradtheoz profile image
muradtheOZ

I thought you will use premade hooks that react provides! but you are using custom hook