# AppRouter

A component that sets up Squide's primitives with a React Router instance.

# Reference

<AppRouter waitForMsw={boolean} waitForPublicData={boolean} waitForProtectedData={boolean}>
    {({ rootRoute, registeredRoutes, routerProviderProps }) => ( ... )}
</AppRouter>

# Properties

  • waitForMsw: A boolean value indicating whether or not Squide should delay the rendering of the requested page until the Mock Service Worker request handlers are registered.
  • waitForPublicData: An optional boolean value indicating whether or not Squide should delay the rendering of the requested page until the public data is ready. The default value is false.
  • waitForProtectedData: An optional boolean value indicating whether or not Squide should delay the rendering of the requested page until the protected data is ready. The default value is false.
  • children: A render function defining a RouterProvider component with rootRoute, registeredRoutes and routerProviderProps.

# 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.

host/src/App.tsx
import { AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

export function App() {
    <AppRouter waitForMsw={false}>
        {({ rootRoute, registeredRoutes, routerProviderProps }) => {
            return (
                <RouterProvider
                    router={createBrowserRouter([
                        {
                            element: rootRoute,
                            children: registeredRoutes
                        }
                    ])}
                    {...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.

host/src/App.tsx
import { useIsBootstrapping, AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter, Outlet } from "react-router-dom";

function BootstrappingRoute() {
    if (useIsBootstrapping()) {
        return <div>Loading...</div>;
    }

    return <Outlet />;
}

export function App() {
    return (
        <AppRouter waitForMsw={false}>
            {({ rootRoute, registeredRoutes, routerProviderProps }) => {
                return (
                    <RouterProvider
                        router={createBrowserRouter([
                            {
                                element: rootRoute,
                                children: [
                                    {
                                        element: <BootstrappingRoute />,
                                        children: registeredRoutes
                                    }
                                ]
                            }
                        ])}
                        {...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.

host/src/RootErrorBoundary.tsx
import { useRouteError } from "react-router-dom";

export function RootErrorBoundary() {
    const error = useRouteError();

    return (
        <div>
            <h2>Unmanaged error</h2>
            <p>An unmanaged error occurred and the application is broken, try refreshing your browser.</p>
        </div>
    );
}
host/src/App.tsx
import { AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { RootErrorBoundary } from "./RootErrorBoundary.tsx";

export function App() {
    return (
        <AppRouter waitForMsw={false}>
            {({ rootRoute, registeredRoutes, routerProviderProps }) => {
                return (
                    <RouterProvider
                        router={createBrowserRouter([
                            {
                                element: rootRoute,
                                errorElement: <RootErrorBoundary />,
                                children: registeredRoutes
                            }
                        ])}
                        {...routerProviderProps}
                    />
                );
            }}
        </AppRouter>
    );
}

# Delay rendering until MSW is ready

host/src/App.tsx
import { AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

export function App() {
    return (
        <AppRouter waitForMsw>
            {({ rootRoute, registeredRoutes, routerProviderProps }) => {
                return (
                    <RouterProvider
                        router={createBrowserRouter([
                            {
                                element: rootRoute,
                                children: registeredRoutes
                            }
                        ])}
                        {...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.

host/src/App.tsx
import { useIsBootstrapping, usePublicDataQueries, AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter, Outlet } 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 
            waitForMsw
            waitForPublicData
        >
            {({ rootRoute, registeredRoutes, routerProviderProps }) => {
                return (
                    <RouterProvider
                        router={createBrowserRouter([
                            {
                                element: rootRoute,
                                children: [
                                    {
                                        element: <BootstrappingRoute />,
                                        children: registeredRoutes
                                    }
                                ]
                            }
                        ])}
                        {...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.

host/src/App.tsx
import { useIsBootstrapping, useProtectedDataQueries, AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter, Outlet } 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 
            waitForMsw
            waitForProtectedData
        >
            {({ rootRoute, registeredRoutes, routerProviderProps }) => {
                return (
                    <RouterProvider
                        router={createBrowserRouter([
                            {
                                element: rootRoute,
                                children: [
                                    {
                                        element: <BootstrappingRoute />,
                                        children: registeredRoutes
                                    }
                                ]
                            }
                        ])}
                        {...routerProviderProps}
                    />
                );
            }}
        </AppRouter>
    );
}