Dhruv Kumar Jha
Dhruv Kumar Jha10mo ago

Options for managing non-persistent state

Hello, I am wondering how everyone manages their app state? (Context: next js app with convex and clerk auth) With convex I don't have to worry about managing most of my state & it's realtime (amazing) However how do you manage global state that is not related to / stored in the database? Does convex have any helpers for this? For example: Let's consider we want to support dark+light mode in our app and we don't want to persist this info in convex, how should we do this with convex? are there any examples/tutorials related to this? Another example could be toggling sidebar - from various different components, how should we go about this? If possible, I'd prefer not to use any state management lib for this.
9 Replies
erquhart
erquhart10mo ago
I have a pretty complex application and I only use Convex and React's state management utilities, specifically useState and contexts (no reducers). In most cases useState() captures state well for a compoent and it's children. If state is more widespread, like your dark/light mode example, you may want to store it in a context so any component can hook in. I have not identified any pain points with this approach, and I love the simplicity.
deen
deen10mo ago
I recommend checking out Jotai - while it can be considered a state management lib, it's extremely simple and lightweight - essentially it's like a globally accessible useState. I use it for simple, non-critical state like a sidebar toggle as you describe. It also has convenient helpers for things like persisting state to localStorage, and is safe to use with SSR/Next.js etc.
erquhart
erquhart10mo ago
Jotai does look like a nice set of abstractions. I don't use next or SSR, good points to consider there if you are.
Dhruv Kumar Jha
Dhruv Kumar JhaOP10mo ago
Thank you @erquhart @deen So it's either context api Or external libs for global state management. Is there anything recommended by convex officially?
erquhart
erquhart10mo ago
It's a bit out of scope for Convex how you handle your non-convex client state. I'm not aware of any official recommendations for that. Really comes down to your preference. Jotai looks like a really clean way to handle your examples, and would probably have a bit less boilerplate than React context. Here's what a global state manager would look like in React without an external lib:
// GlobalState.tsx
type Theme = 'light' | 'dark'
interface GlobalState {
theme: Theme
setTheme: React.Dispatch<React.SetStateAction<Theme>>
sidebarOpen: boolean
setSidebarOpen: (open: boolean) => void
}

const GlobalStateContext = createContext<GlobalState>({} as GlobalState)

const GlobalStateProvider = ({ children }: PropsWithChildren) => {
const [theme, setTheme] = useState<Theme>('light')
const [sidebarOpen, setSidebarOpen] = useState(false)

const context = {
theme,
setTheme,
sidebarOpen,
setSidebarOpen,
}
return (
<GlobalStateContext.Provider value={context}>
{children}
</GlobalStateContext.Provider>
)
}

const useGlobalState = () => {
const context = useContext(GlobalStateContext)
if (!Object.keys(context).length) {
throw new Error('useGlobalState must be used within GlobalStateProvider')
}
return context
}

// App.tsx
const App = () => {
return (
<GlobalStateProvider>
<Main />
</GlobalStateProvider>
)
}

// SomeComponent.tsx
const SomeComponent = () => {
const { theme, setTheme } = useGlobalState()
return (
<div>
<p>Current theme: {theme}</p>
<button
onClick={() =>
setTheme((currentTheme) =>
currentTheme === 'light' ? 'dark' : 'light',
)
}
>
Toggle theme
</button>
</div>
)
}
// GlobalState.tsx
type Theme = 'light' | 'dark'
interface GlobalState {
theme: Theme
setTheme: React.Dispatch<React.SetStateAction<Theme>>
sidebarOpen: boolean
setSidebarOpen: (open: boolean) => void
}

const GlobalStateContext = createContext<GlobalState>({} as GlobalState)

const GlobalStateProvider = ({ children }: PropsWithChildren) => {
const [theme, setTheme] = useState<Theme>('light')
const [sidebarOpen, setSidebarOpen] = useState(false)

const context = {
theme,
setTheme,
sidebarOpen,
setSidebarOpen,
}
return (
<GlobalStateContext.Provider value={context}>
{children}
</GlobalStateContext.Provider>
)
}

const useGlobalState = () => {
const context = useContext(GlobalStateContext)
if (!Object.keys(context).length) {
throw new Error('useGlobalState must be used within GlobalStateProvider')
}
return context
}

// App.tsx
const App = () => {
return (
<GlobalStateProvider>
<Main />
</GlobalStateProvider>
)
}

// SomeComponent.tsx
const SomeComponent = () => {
const { theme, setTheme } = useGlobalState()
return (
<div>
<p>Current theme: {theme}</p>
<button
onClick={() =>
setTheme((currentTheme) =>
currentTheme === 'light' ? 'dark' : 'light',
)
}
>
Toggle theme
</button>
</div>
)
}
But dang I am now looking at the Jotai docs and thinking how nice it might be to (someday) get rid of my global contexts. Jotai looks to have optimizations that I don't, and that I'm unlikely to implement unless forced. Nesting contexts can be particularly helpful, but I barely use them that way, if at all.
deen
deen10mo ago
Convex is a backend with some simple but powerful tools for querying and manipulating data in the front end - its solution would be to store this data in the database. Take a look at this article about presence: https://stack.convex.dev/presence-with-convex as well as the other posts on https://stack.convex.dev about working with session data. You could store this data in a row of keys/values tied to a session id. Querying/mutating a simple row with an index is very fast, and you could use optimistic mutations to make changes appear instant. But I'd just use a simple Jotai atom. If you're concerned about needing an external lib, compared to the size of say, Clerk, the module size is negligible - a few kilobytes. I'm using it with the same Next/Convex/Clerk set up as you.
Implementing Presence with Convex
Some patterns for incorporating presence into a web app leveraging Convex, and sharing some tips & utilities I built along the way.
Stack
Bright ideas and techniques for building with Convex.
Dhruv Kumar Jha
Dhruv Kumar JhaOP10mo ago
Amazing, thank you.
deen
deen10mo ago
I don't understand why it's not more popular, it fits in as an extension to the React paradigm so neatly. Perhaps because the documentation is a bit sparse, or terms like "atoms" sounding scary. You can implement isolated context using provider stores with jotai if you want to, but I've also never felt the need. I haven't really used it for much beyond some simple global useStates, but I'm currently experimenting with dynamically creating memoized atoms for values in a complex form, for easily maintaining persistence and limiting re-renders to the relevant input components.
Dhruv Kumar Jha
Dhruv Kumar JhaOP10mo ago
I have used recoil in the past, was exploring https://github.com/pmndrs/zustand, looks like a pretty good solution for my usecase. Will checkout jotai before finalizing one.

Did you find this page helpful?