Global state management for Astro + React Query, designed specifically for SSR/hydration safety in Astro's island architecture.
The API is final and won't change. For the use cases tested so far, Hydration Mismatch is not a problem anymore (both in development and build). If you encounter any issues, please report them as an issue so we can move this to v1 soon.
npm install astro-global-state @tanstack/react-queryImportant: Initialize global state early in your component tree. The component that first calls
useGlobal(key, value)should be rendered before any component that accesses or depends on that state.
import { useGlobal, withReactQuery } from "astro-global-state";
function LanguageSelectorInner() {
const [language, setLanguage] = useGlobal<"en" | "pt">("LANGUAGE", "en");
return (
<select value={language} onChange={(e) => setLanguage(e.target.value as "en" | "pt")}>
<option value="en">English</option>
<option value="pt">Português</option>
</select>
);
}
export const LanguageSelector = withReactQuery(LanguageSelectorInner);import { useGlobal, withReactQuery } from "astro-global-state";
const translations = {
en: { greeting: "Hello", farewell: "Goodbye" },
pt: { greeting: "Olá", farewell: "Adeus" },
};
function TranslatedContentInner() {
// here, as the value is already globally set, you don't pass the second argument as to not override the original one
const [language] = useGlobal<"en" | "pt">("LANGUAGE");
return (
<div>
<h1>{translations[language].greeting}</h1>
<p>{translations[language].farewell}</p>
</div>
);
}
export const TranslatedContent = withReactQuery(TranslatedContentInner);import { useGlobal, withReactQuery } from "astro-global-state";
function AnalyticsInner() {
const { get } = useGlobal();
const handleClick = () => {
// Gets current language WITHOUT subscribing to changes
// This function won't re-run when language changes
const currentLanguage = get("LANGUAGE");
console.log(`User clicked in ${currentLanguage}`);
};
return <button onClick={handleClick}>Track Event</button>;
}
export const Analytics = withReactQuery(AnalyticsInner);Global state management hook using React Query's cache as the store. Must be used inside a component wrapped with withReactQuery.
Parameters:
key: State key (string or string[])initialData: Optional initial value or initializer function
Returns:
[value, setValue, refresh, reset]- Tuple with state value, setter, refresh, and reset functions
Example:
const [user, setUser, refresh, reset] = useGlobal<User>("CURRENT_USER", null);
setUser({ name: "John" }); // Update state
refresh(); // Invalidate cache (triggers re-render)
reset(); // Remove from cacheAccessor mode - get any global value without subscribing to updates.
Returns:
{ get: <R>(key) => R | null }- Function to access cached values
Example:
const { get } = useGlobal();
const theme = get("THEME"); // No re-render on updateHigher-order component that automatically wraps a component in ReactQueryProvider. Enables all React Query hooks including useGlobal to work correctly.
Parameters:
Component: Any React component
Returns:
- Wrapped component with React Query context
Example:
function MyComponent() {
const [data, setData] = useGlobal("KEY", null);
return <div>{data}</div>;
}
export default withReactQuery(MyComponent);- Singleton QueryClient: A single QueryClient instance persists globally, ensuring all islands share the same cache
- React Query Cache: Global state is stored in React Query's query cache with infinite staleTime
- Hydration Safe: Automatically handles SSR/client hydration boundaries in Astro
- Fallback Mechanism: If
useGlobalis called outside a provider, it falls back to the global QueryClient
const [language, setLanguage] = useGlobal<"en" | "pt">("LANGUAGE", "en");const [isDark, setIsDark] = useGlobal<boolean>("DARK_MODE", false);const [user, setUser] = useGlobal(["app", "user"], null);
const [theme, setTheme] = useGlobal(["app", "theme"], "light");- Always wrap components with
withReactQuery- Required foruseGlobalto work - Use descriptive key names - Helps with debugging and cache visibility
- Initialize with appropriate defaults - Prevents undefined issues
- Keep state as simple as possible - Use React Query's
useQueryfor server state - Use accessor mode for read-only access - Avoids unnecessary re-renders when you only need to read values
Ensure your component is wrapped with withReactQuery. This automatically provides the React Query context needed for useGlobal to work.
Check that your component is wrapped with withReactQuery. useGlobal can only be called inside components that are wrapped with this HOC.
Hydration mismatches typically occur when SSR and client render different values. Initialize useGlobal with consistent data (avoid random values during initialization).
All components using useGlobal share the same React Query client cache. Use consistent key names to share state across components.
MIT