#
Fetch public global data
To make global public data fetching easier, Squide provides primitives build on top of TanStack Query to orchestrate both the data-loading states and the associated UI.
At first glance, one might wonder what could be so complicated about fetching the global data of an application. It's only fetches ...right? Well, there are several concerns to take into account for a modular application:
- When in development, the global data cannot be fetched until the Mock Service Worker (MSW) request handlers are registered and MSW is ready.
- To register the MSW request handlers, the modules must be registered first.
- If the requested page is public, only the global public data should be fetched.
- If the requested page is protected, both the global public and protected data should be fetched.
- The requested page rendering must be delayed until the global data has been fetched.
- A unique loading spinner should be displayed to the user during this process, ensuring there's no flickering due to different spinners being rendered.
Before fetching data with TanStack Query, start by following the setup Tanstack Query integration guide to setup the Query client and the React provider. Once the setup is complete, the examples below cover the most common use cases.
For more details, refer to the reference documentation.
#
Fetch data
👉 There are four key steps to fetch global public data:
- Set the
waitForPublicDataprop of the AppRouter component totrue. - Fetch the data using the usePublicDataQueries hook.
- Use the useIsBootstrapping hook to display a loading spinner while the data is being retrieved.
- Forward the data to the pages through a React context.
Here's an example:
import { AppRouter, usePublicDataQueries, useIsBootstrapping } from "@squide/firefly";
import { createBrowserRouter, Outlet } from "react-router";
import { RouterProvider } from "react-router/dom";
import { FetchCountContext } from "@sample/shared";
function BootstrappingRoute() {
const [fetchCount] = usePublicDataQueries([
{
queryKey: ["/api/count"],
queryFn: async () => {
const response = await fetch("/api/count");
if (!response.ok) {
throw new Error("Cannot fetch data!");
}
const data = await response.json();
return data.count as number;
}
}
]);
if (useIsBootstrapping()) {
return <div>Loading...</div>;
}
return (
<FetchCountContext.Provider value={fetchCount}>
<Outlet />
</FetchCountContext.Provider>
);
}
export function App() {
return (
<AppRouter waitForPublicData>
{({ rootRoute, registeredRoutes, routerProviderProps }) => {
return (
<RouterProvider
router={createBrowserRouter([
{
element: rootRoute,
children: [
{
element: <BootstrappingRoute />,
children: registeredRoutes
}
]
}
])}
{...routerProviderProps}
/>
);
}}
</AppRouter>
);
}
import { createContext, useContext } from "react";
export const FetchCountContext = createContext(0);
export function useFetchCount() {
return useContext(FetchCountContext);
}
First, define an MSW request handler that returns the number of times it has been fetched:
import { HttpResponse, http, type HttpHandler } from "msw";
let fetchCount = 0;
export const requestHandlers: HttpHandler[] = [
http.get("/api/count", () => {
fetchCount += 1;
return HttpResponse.json([{
"count": fetchCount
}]);
})
];
Then, register the request handler using the module registration function:
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
export const register: ModuleRegisterFunction<FireflyRuntime> = async runtime => {
if (runtime.isMswEnabled) {
// Files that includes an import to the "msw" package are included dynamically to prevent adding
// unused MSW code to the production bundles.
const requestHandlers = (await import("../mocks/handlers.ts")).requestHandlers;
runtime.registerRequestHandlers(requestHandlers);
}
}
import { useFetchCount } from "@sample/shared";
export function Page() {
const fetchCount = useFetchCount();
const isOdd = fetchCount % 2 === 0;
return (
<p style={{ backgroundColor: isOdd ? "green" : undefined }}>
When the fetch count is odd, the background should be green.
</p>
)
}
#
Handle fetch errors
The usePublicDataQueries hook can throw GlobalDataQueriesError instances, which are typically unmanaged and should be handled by an error boundary. To assert in an error boundary that an error is an instance of GlobalDataQueriesError, use the isGlobalDataQueriesError function:
import { useLogger, isGlobalDataQueriesError } from "@squide/firefly";
import { useLocation, useRouteError } from "react-router/dom";
export function ErrorBoundary() {
const error = useRouteError() as Error;
const location = useLocation();
const logger = useLogger();
useEffect(() => {
if (isGlobalDataQueriesError(error)) {
logger
.withText(`[shell] An unmanaged error occurred while rendering the route with path ${location.pathname} ${error.message}`)
.withError(error.errors)
.error();
}
}, [location.pathname, error, logger]);
return (
<div>
<h2>Unmanaged error</h2>
<p>An unmanaged error occurred and the application is broken, try refreshing your browser.</p>
</div>
);
}
#
Register a conditionnal navigation item
To register a navigation item based on public remote data, refer to the register deferred navigation items guide.