AppRouter
A component that sets up Squide's primitives with a React Router instance.
Reference
<AppRouter waitForPublicData={boolean} waitForProtectedData={boolean} strictMode={boolean}>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => ( ... )}
</AppRouter>
Properties
waitForPublicData: An optionalbooleanvalue indicating whether or not Squide should delay the rendering of the requested page until the public data is ready. The default value isfalse.waitForProtectedData: An optionalbooleanvalue indicating whether or not Squide should delay the rendering of the requested page until the protected data is ready. The default value isfalse.strictMode: An optionalbooleanvalue indicating whether or not Squide should validate the registrations once the modules are ready and throw when the active location matches no registered route. The default value istrue.children: A render function defining a RouterProvider component withrootRoute,registeredRoutes,routerPropsandrouterProviderProps.
Usage
Define a router provider
The rootRoute component provided as an argument to the AppRouter rendering function must always be rendered as a parent of the registeredRoutes.
import { AppRouter } from "@squide/firefly";
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
export function App() {
<AppRouter >
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => {
return (
<RouterProvider
router={createBrowserRouter([
{
element: rootRoute,
children: registeredRoutes
}
], routerProps)}
{...routerProviderProps}
/>
);
}}
</AppRouter>
}
Define a loading component
A BootstrappingRoute component is introduced in the following example because the useIsBootstrapping hook must be rendered as a child of rootRoute.
import { useIsBootstrapping, AppRouter } from "@squide/firefly";
import { createBrowserRouter, Outlet } from "react-router";
import { RouterProvider } from "react-router/dom";
function BootstrappingRoute() {
if (useIsBootstrapping()) {
return <div>Loading...</div>;
}
return <Outlet />;
}
export function App() {
return (
<AppRouter>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => {
return (
<RouterProvider
router={createBrowserRouter([
{
element: rootRoute,
children: [
{
element: <BootstrappingRoute />,
children: registeredRoutes
}
]
}
], routerProps)}
{...routerProviderProps}
/>
);
}}
</AppRouter>
);
}
Define a root error boundary
A React Router errorElement retrieves the current error using the useRouteError hook. The root error boundary should always wrap the registeredRoutes and, when application, the BootstrapingRoute component.
import { isGlobalDataQueriesError, useLogger } from "@squide/firefly";
import { useRouteError, isRouteErrorResponse } from "react-router";
import { useEffect } from "react";
export function RootErrorBoundary() {
const error = useRouteError() as Error;
const location = useLocation();
const logger = useLogger();
useEffect(() => {
if (isRouteErrorResponse(error)) {
logger.error(`An unmanaged error occurred while rendering the route with path ${location.pathname} ${error.status} ${error.statusText}.`);
} else if (isGlobalDataQueriesError(error)) {
logger
.withText(`An unmanaged error occurred while rendering the route with path ${location.pathname}:`)
.withText(error.message)
.withObject(error.errors)
.error();
} else {
logger
.withText(`[shell] An unmanaged error occurred while rendering the route with path ${location.pathname}:`)
.withError(error)
.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>
);
}
import { AppRouter } from "@squide/firefly";
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import { RootErrorBoundary } from "./RootErrorBoundary.tsx";
export function App() {
return (
<AppRouter>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => {
return (
<RouterProvider
router={createBrowserRouter([
{
element: rootRoute,
errorElement: <RootErrorBoundary />,
children: registeredRoutes
}
], routerProps)}
{...routerProviderProps}
/>
);
}}
</AppRouter>
);
}
Delay rendering until the public data is ready
A BootstrappingRoute component is introduced in the following example because the usePublicDataQueries hook must be rendered as a child of rootRoute.
import { useIsBootstrapping, usePublicDataQueries, AppRouter } from "@squide/firefly";
import { createBrowserRouter, Outlet } from "react-router";
import { RouterProvider } from "react-router/dom";
import { FeatureFlagsContext } from "@sample/shared";
import { getFeatureFlagsQuery } from "./getFeatureFlagsQuery.ts";
function BootstrappingRoute() {
const [featureFlags] = usePublicDataQueries([getFeatureFlagsQuery]);
if (useIsBootstrapping()) {
return <div>Loading...</div>;
}
return (
<FeatureFlagsContext.Provider value={featureFlags}>
<Outlet />
</FeatureFlagsContext.Provider>
);
}
export function App() {
return (
<AppRouter waitForPublicData>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => {
return (
<RouterProvider
router={createBrowserRouter([
{
element: rootRoute,
children: [
{
element: <BootstrappingRoute />,
children: registeredRoutes
}
]
}
], routerProps)}
{...routerProviderProps}
/>
);
}}
</AppRouter>
);
}
Delay rendering until the protected data is ready
A BootstrappingRoute component is introduced in the following example because the useProtectedDataQueries hook must be rendered as a child of rootRoute.
import { useIsBootstrapping, useProtectedDataQueries, AppRouter } from "@squide/firefly";
import { createBrowserRouter, Outlet } from "react-router";
import { RouterProvider } from "react-router/dom";
import { SessionContext, isApiError } from "@sample/shared";
import { getSessionQuery } from "./getSessionQuery.ts";
function BootstrappingRoute() {
const [session] = useProtectedDataQueries(
[getSessionQuery],
error => isApiError(error) && error.status === 401
);
if (useIsBootstrapping()) {
return <div>Loading...</div>;
}
return (
<SessionContext.Provider value={session}>
<Outlet />
</SessionContext.Provider>
);
}
export function App() {
return (
<AppRouter waitForProtectedData>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => {
return (
<RouterProvider
router={createBrowserRouter([
{
element: rootRoute,
children: [
{
element: <BootstrappingRoute />,
children: registeredRoutes
}
]
}
], routerProps)}
{...routerProviderProps}
/>
);
}}
</AppRouter>
);
}
Disable strict mode
By default, Squide validates the registrations once the modules are ready. A navigation item registered under a section that no module registered, or a route registered under a parent that does not exist, throws in development and is logged in production.
Strict mode additionally throws when the active location matches no registered route.
Set strictMode to false to turn all of it off:
import { AppRouter } from "@squide/firefly";
export function App() {
return (
<AppRouter strictMode={false}>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => ( ... )}
</AppRouter>
);
}
Strict mode is what surfaces a misconfigured sectionId, parentPath or parentId. The registration is dropped either way, turning the validation off only removes the report.