Skip to Content

Context State

Exports Size
loading...
Gzip Size
loading...
Brotli Size
Source Code
View on GitHub
Docs
Edit this page

You don’t need a global state management library like Redux or Zustand for every state in your app. Just store your shared state that lives only in React (without using any global state management library). Lift your state up and passing them deeply into your React app with React Context , without worrying about performance.

Looking for a state-management with a similar API, that can persisting state into localStorage or sessionStorage? Check out createLocalStorageState and createSessionStorageState.

Looking for a reducer-based context state management? Check out createContextReducer.

Usage

First, create a shared state provider along with getter and setter hooks with createContextState. It is recommended to place them in a separate file:

src/context/sidebar-active.tsx
'use client'; import { createContextState } from 'foxact/context-state'; // createContextState is also available from `foxact/create-context-state`: // import { createContextState } from 'foxact/create-context-state'; // By retutning an array, `createContextState` allows you to name your provider and hooks as you like: export const [SidebarActiveProvider, useSidebarActive, useSetSidebarActive] = createContextState(false); // You can also create your own hooks on top of the getter and setter hooks: export const useToggleSidebarActive = () => { const setSidebarActive = useSetSidebarActive(); // always use `useCallback` to memoize returned function, just like what `foxact` does: return useCallback(() => setSidebarActive(prevSidebarActive => !prevSidebarActive), [setSidebarActive]); // you can safely add it to the dependency array since `setSidebarActive` is also memoized };

Then, wrap your app with the provider:

src/layout/main-layout.tsx
import { memo } from 'react'; import { SidebarActiveProvider } from '../context/sidebar-active'; export default memo(function MainLayout({ children }: React.PropsWithChildren) { return ( <SidebarActiveProvider> <div> {children} </div> </SidebarActiveProvider> ); });

And now you can use the getter and setter hooks anywhere in your app:

s../../components/sidebar.tsx
import { memo } from 'react'; import { useSidebarActive, useSetSidebarActive } from '../context/sidebar-active'; export default memo(function Sidebar() { const sidebarActive = useSidebarActive(); const setSidebarActive = useSetSidebarActive(); return ( <div className={`sidebar ${sidebarActive ? 'active' : ''}`}> <button onClick={() => setSidebarActive(false)}>Close Sidebar</button> </div> ); });
s../../components/navbar.tsx
import { memo } from 'react'; import { useToggleSidebarActive } from '../context/sidebar-active'; export default memo(function Navbar() { const toggleSidebarActive = useToggleSidebarActive(); return ( <div className="navbar"> <button onClick={toggleSidebarActive}>Menu Button</button> </div> ); });

And when the sidebar active state is changed, only the component that uses useSidebarActive() hook will be re-rendered, in this case the only affected component is <Sidebar />.

Provide initial state within the React tree

The initial state can also be provided within the React tree by passing it as a prop to the provider:

src/layout/main-layout.tsx
import { memo } from 'react'; import { SidebarActiveProvider } from '../context/sidebar-active'; export default memo(function MainLayout({ children }: React.PropsWithChildren) { return ( <SidebarActiveProvider initialState={true}> {children} </SidebarActiveProvider> ); });

Read context state conditionally

Normally, you can not read context state conditionally per Rules of Hooks:

Only Call Hooks at the Top Level: Don’t call Hooks inside loops, conditions, or nested functions.

However, if you do need to read the context state conditionally, you can use the new React.use introduced since React 18.3:

src/context/sidebar-active.tsx
import { createContextState } from 'foxact/context-state'; // createContextState is also available from `foxact/create-context-state`: // import { createContextState } from 'foxact/create-context-state'; export const [SidebarActiveProvider, useSidebarActive, useSetSidebarActive, SidebarActiveContext] = createContextState(false);
../../components/sidebar.tsx
import { memo, use } from 'react'; import { SidebarActiveContext } from '../context/sidebar-active'; interface SidebarProps { loggedIn: boolean } export default memo(function Sidebar({ loggedIn }: SidebarProps) { // This is only for the demonstration purpose to show `React.use` can be called conditionally if (!loggedIn) { return ( <div className="sidebar"> <div>Hello!</div> </div> ) } // Here we are using `use` to read the context state conditionally const sidebarActive = use(SidebarActiveContext); return ( <div className={`sidebar ${sidebarActive ? 'active' : ''}`}> <div>Welcome back, Sukka!</div> </div> ); });