#
Fetch initial data
Before going forward with this guide, make sure that you completed the Setup Mock Service Worker guide.
Retrieving the initial data of an application is a crucial aspect that isn't always straightforward to implement. That's why we encourage feature teams to build their initial data fetching strategy on top of the Squide AppRouter component.
#
Challenges with initial data
At first glance, one might wonder what could be so complicated about fetching the initial data of an application. It's only fetches ...right? Well, there are several concerns to take into account for a Squide application:
When in development, the initial data cannot be fetched until the Mock Service Worker (MSW) request handlers are registered and MSW is started.
To register the MSW request handlers, the remote modules must be registered first.
If the requested page is public, only the initial public data should be fetched.
If the requested page is protected, both the initial public and protected data should be fetched.
The requested page cannot be rendered until the initial data has been fetched.
A unique loading spinner should be displayed to the user while the modules are being registered, the MSW request handlers are being registered and the initial data is being fetched, ensuring no flickering due to different spinners being rendered.
To help manage those concerns, Squide offer an AppRouter
component that takes care of setuping Squide federated primitive and orchestrating the different states.
#
Fetch public data
#
Add an endpoint
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 stuff to the application bundles.
const requestHandlers = (await import("../mocks/handlers.ts")).requestHandlers;
runtime.registerRequestHandlers(requestHandlers);
}
}
#
Create a shared context
Then, in a shared project, create a React context named FetchCountContext
:
import { createContext, useContext } from "react";
export const FetchCountContext = createContext(0);
export function useFetchCount() {
return useContext(FetchCountContext);
}
Ensure that the shared project is configured as a shared dependency.
#
Fetch the data
Finally, open the host application code and update the App
component to utilize the AppRouter
component's onLoadPublicData handler. This handler will fetch the count and forward the retrieved value through FetchCountContext
:
import { useState, useCallback } from "react";
import { AppRouter } from "@squide/firefly";
import { FetchCountContext } from "@sample/shared";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
async function fetchPublicData(setFetchCount: (fetchCount: number) => void, signal: AbortSignal) {
const response = await fetch("/api/count", {
signal
});
const data = await response.json();
setFetchCount(data.count);
}
export function App() {
const [fetchCount, setFetchCount] = useState(0);
const handleLoadPublicData = useCallback((signal: AbortSignal) => {
return fetchPublicData(setFetchCount, signal);
}, []);
return (
<FetchCountContext.Provider value={fetchCount}>
<AppRouter
onLoadPublicData={handleLoadPublicData}
isPublicDataLoaded={!!fetchCount}
fallbackElement={<div>Loading...</div>}
errorElement={<div>An error occured!</div>}
waitForMsw={true}
>
{(routes, providerProps) => (
<RouterProvider router={createBrowserRouter(routes)} {...providerProps} />
)}
</AppRouter>
</FetchCountContext.Provider>
);
}
The onLoadPublicData
handler receives as first argument an AbortSignal that should be forwarded to the HTTP client initiating the public data GET request. The signal will cancel the previous HTTP request when the onLoadPublicData
handler is called twice due to the AppRouter
being re-rendered.
The isPublicDataLoaded
must be provided to indicate whether or not the public data loading is completed.
#
Use the endpoint data
Now, create a InitialDataLayout
component that utilizes the count retrieved from FetchCountContext
and render pages with a green background color if the value is odd:
import { useFetchCount } from "@sample/shared";
import { Outlet } from "react-router-dom";
export function InitialDataLayout() {
const fetchCount = useFetchCount();
const isOdd = fetchCount % 2 === 0;
return (
<div style={{ backgroundColor: isOdd ? "green" : undefined }}>
<Outlet />
</div>
)
}
Then, create a Page
component:
export function Page() {
return (
<div>When the fetch count is odd, my background should be green.</div>
)
}
Finally, register both components:
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { InitialDataLayout } from "./InitialDataLayout.tsx";
import { Page } from "./Page.tsx";
export const register: ModuleRegisterFunction<FireflyRuntime> = async runtime => {
runtime.registerRoute({
path: "/initial-data",
element: <InitialDataLayout />,
children: [
{
index: true,
element: <Page />
}
]
});
runtime.registerNavigationItem({
$label: "Initial data Page",
to: "/initial-data"
});
// Files that includes an import to the "msw" package are included dynamically to prevent adding
// unused MSW stuff to the application bundles.
const requestHandlers = (await import("../mocks/handlers.ts")).requestHandlers;
runtime.registerRequestHandlers(requestHandlers);
}
#
Try it 🚀
Start the application in a development environment using the dev
script and navigate to the /initial-data
page. Refresh the page a few times, the background color should alternate between transparent and green.
#
Troubleshoot issues
If you are experiencing issues with this section of the guide:
- Open the DevTools console. You'll find a log entry for each registration that occurs (including MSW request handlers) and error messages if something went wrong.
- Refer to a working example on GitHub.
- Refer to the troubleshooting page.
#
Fetch protected data
Now, let's load protected data. The process is similar to fetching public data, but this time, we'll use the onLoadProtectedData handler of the AppRouter
component instead.
#
Add an endpoint
First, define a MSW request handler that returns a user tenant subscription data:
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
}]);
}),
http.get("/api/subscription", () => {
// NOTE:
// The user id should be retrieved from the current session and the subscription should be retrieved from a database with this id.
// For the sake of simplicity, we haven't done it for this guide, instead we return hardcoded data.
return HttpResponse.json([{
"status": "paid"
}]);
})
];
If you've registered the
In the previous code sample, for the sake of simplicity, we haven't secured the request handler or implemented session management. However, please be aware that you should do it for a real Workleap application.
#
Create a shared context
Then, in a shared project, create a SubscriptionContext
:
import { createContext, useContext } from "react";
export interface Subscription {
status: string
}
export const SubscriptionContext = createContext(Subscription | undefined);
export function useSubscription() {
return useContext(SubscriptionContext);
}
Ensure that the shared project is configured as a shared dependency.
#
Fetch the data
Finally, open the host application code and update the App
component to utilize the AppRouter
component's onLoadProtectedData
handler. This handler will fetch the user tenant subscription and forward the retrieved value through SubscriptionContext
:
import { useState, useCallback } from "react";
import { AppRouter } from "@squide/firefly";
import { FetchCountContext, SubscriptionContext, type Subscription } from "@sample/shared";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
async function fetchPublicData(setFetchCount: (fetchCount: number) => void, signal: AbortSignal) {
const response = await fetch("/api/count", {
signal
});
const data = await response.json();
setFetchCount(data.count);
}
async function fetchProtectedData(setSubscription: (subscription: Subscription) => void, signal: AbortSignal) {
const response = await fetch("/api/subscription", {
signal
});
const data = await response.json();
const subscription: Subscription = {
status: data.status
};
setSubscription(subscription);
}
export function App() {
const [fetchCount, setFetchCount] = useState(0);
const [subscription, setSubscription] = useState<Subscription>();
const handleLoadPublicData = useCallback((signal: AbortController) => {
return fetchPublicData(setFetchCount, signal);
}, []);
const handleLoadProtectedData = useCallback((signal: AbortController) => {
return fetchProtectedData(setSubscription, signal);
}, []);
return (
<FetchCountContext.Provider value={fetchCount}>
<SubscriptionContext.Provider value={subscription}>
<AppRouter
onLoadPublicData={handleLoadPublicData}
onLoadProtectedData={handleLoadProtectedData}
isPublicDataLoaded={!!fetchCount}
isProtectedDataLoaded={!!subscription}
fallbackElement={<div>Loading...</div>}
errorElement={<div>An error occured!</div>}
waitForMsw={true}
>
{(routes, providerProps) => (
<RouterProvider router={createBrowserRouter(routes)} {...providerProps} />
)}
</AppRouter>
<SubscriptionContext.Provider />
</FetchCountContext.Provider>
);
}
The onLoadProtectedData
handler receives as first argument an AbortSignal that should be forwarded to the HTTP client initiating the public data GET request. The signal will cancel the previous HTTP request when the onLoadProtectedData
handler is called twice due to the AppRouter
being re-rendered.
The isProtectedDataLoaded
must be provided to indicate whether or not the protected data loading is completed.
#
Use the endpoint data
Now, update the InitialDataLayout
component that was previously created for the
import { useFetchCount, useSubscription } from "@sample/shared";
import { Outlet } from "react-router-dom";
export function InitialDataLayout() {
const fetchCount = useFetchCount();
const subscription = useSubscription();
const isOdd = fetchCount % 2 === 0;
return (
<div>Subscription status: {subscription?.status}</div>
<div style={{ backgroundColor: isOdd ? "green" : undefined }}>
<Outlet />
</div>
)
}
#
Try it 🚀
Start the application in a development environment using the dev
script and navigate to the /initial-data
page. You should notice the subscription status.
#
Troubleshoot issues
If you are experiencing issues with this section of the guide:
- Open the DevTools console. You'll find a log entry for each registration that occurs (including MSW request handlers) and error messages if something went wrong.
- Refer to a working example on GitHub.
- Refer to the troubleshooting page.