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.