localStorage is used almost in every React project, but is it easy to manage it, do we need a lib for this and what do we have?
There are popular ways of handing auth: one way we store JWT token in localStorage, in other way we store it in httpOnly cookie which is more secure. But even in second way it still makes sense to store authorizedUserId
id or at least isAuthorized
boolean in localStorage to not make redundant requests to a server side later, remove it when logging out or when server responds with '401 Not Authorized' code.
If after performing authorization request we simply set a value of authorized user to the localStorage, other components won't be updated automatically, because React is not aware of state change.
How localStorage is usually handled in React
One popular option is Context API, documentation even says it's good for storing current user (here).
Another popular way is to use a state manager.
Why I'd like to avoid both of them:
boilerplate: they both requires writing much more code than we want to, yes, even with Redux Toolkit, need to write slice, add it to global store, if we are using TypeScript it's even more code since both Context and state manager cannot infer type from initial data
synchronization of two sources of truth is error prone: each change of value in the store must be reflected by setting value to localStorage, if localStorage was updated in a separate browser tab the change must be saved to the store
efficiency: Context is known to be less efficient than a state manager. With state manager you have to read, parse and validate the value at every page load when store or context is initializing. In case of global Redux it means that even if we want to handle some specific value only in some distant page of our app, it has to be read, parsed and validated to initialize our global monolithic store, no matter what page is currently open.
The simplest solution
Here is my library: react-local-storage-manager
Let's consider example of storing authToken
:
first, create a separate file for a 'store', it may be named auth.store.js or as you like:
// auth.store.js
import createLocalStore from 'react-local-storage-manager'
const store = createLocalStore(
'authToken', // local storage key
(id) => isNaN(id) ? undefined : id,
)
// export setter function:
export const saveAuthToken = store.set
// export getter function:
export const getAuthToken = store.get
// export hook to use a value:
export const useAuthToken = store.use
// export other useful hooks:
export const useIsAuthorized = () =>
Boolean(useAuthToken()) // token to boolean
And now it's so easy to get, set, use:
// set token after successful authorization result:
const result = await callApiToAuthorize(loginData)
saveAuthorizedUserId(result.token)
// to keep track of authorized state in any component:
const isAuthorized = useIsAuthorized()
// get token outside of component:
const token = getCurrentUserToken()
With the library values are read, parsed and validated only when accessing them and only for the first time, second time it will get value from cache. Validation is enforced to not be forgotten, multiple tabs will be synchronized.
To see it in action check this codesandbox, one more example of shopping cart is in readme on github.
Please share you thoughts!
Top comments (0)