Update (global) state outside of React: Jotai's store API
Technically, we're still in React just outside of its lifecycle.
Scenario
A Jotai atom that manages the app's notifications state.
export type Notification = {
id: string
type: 'info' | 'warning' | 'success' | 'error'
title: string
message?: string
}
type notification = Omit<Notification, `id`>
export const notificationsAtom = atom<Notification[]>([])
Using Axios interceptors to intercept API requests to update the notifications state; for proper error handling and better UX, update the notifications state in case of potential errors. Since we're not in the React lifecycle, generally speaking, apparently, we can't use useAtom - rules of hooks.
Sure, I could just write a React component that does the intercepting logic like so:
import { atom, useSetAtom } from 'jotai'
import { useEffect } from 'react'
import { instance } from './lib/axios'
export const notificationsAtom = atom([])
export function APIInterceptor() {
const setNotifications = useSetAtom(notificationsAtom)
useEffect(() => {
instance.interceptors.response.use(
({ data }) => {
return data
},
(error) => {
const message = error.response?.data?.message || error.message
setNotifications((prevAtomVal) => [
...prevAtomVal,
{ type: `error`, title: `Error`, message },
])
return Promise.reject(error)
}
)
}, [])
return
}
But that's rather inconvenient or could be, that the component has to be imported to run the logic and lets not forget additional TypeScript types too, it just seems a little bit much for an API interceptor logic.
import { useAtomValue } from 'jotai'
console.log(useAtomValue(notificationsAtom))
The Store API
Available in Jotai v2, the store API lets you access atoms outside of React's lifecycle. Store features two functions/variants:
createStore - used with the Provider component.
getDefaultStore, lets you access a store in provider-less mode if you are not using the Provider component to provide state across components.
I reached out to the author of Jotai, and he gave a concise explanation and simplified the whole sleuthing process for me, and I got a solution!
Solution
const defaultStore = getDefaultStore()
export const instance = Axios.create({
baseURL: API_URL,
})
instance.interceptors.response.use(
({ data }) => {
return data
},
(error) => {
const message = error.response?.data?.message || error.message
defaultStore.set(notificationsAtom, (prevAtomVal) => [
...prevAtomVal,
{ id: uuID, type: `error`, title: `Error`, message },
])
return Promise.reject(error)
}
)
Shout out to Daishi Kato - the author of Jotai and two other state management libraries — Zustand & Valtio (https://docs.pmnd.rs)
If you have questions regarding Jotai, I highly recommend the Discord channel.
Subscribe to my newsletter
Read articles from Muhammed Tijani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Muhammed Tijani
Muhammed Tijani
An aesthete. I use Web APIs & terminal commands.