= runtime =>
```
!!!info
-This guide only covers the `RootLayout` and `RootErrorBoundary` components but the same goes for other shell assets such as an `AuthenticationBoundary` component.
+This guide only covers the `RootLayout`, `RootErrorBoundary` and `ModuleErrorBoundary` components but the same goes for other shell assets such as an `AuthenticationBoundary` component.
!!!
## Update the host application
diff --git a/docs/guides/isolate-module-failures.md b/docs/guides/isolate-module-failures.md
index cb89e3f99..e8a97b0a1 100644
--- a/docs/guides/isolate-module-failures.md
+++ b/docs/guides/isolate-module-failures.md
@@ -12,10 +12,10 @@ Nevertheless, an application, federated or non-federated, can get very close to
## Create an error boundary
-First, define a React Router's error boundary to catch module errors. For this example we'll name it `RootErrorBoundary`:
+First, define a React Router's error boundary to catch module errors. For this example we'll name it `ModuleErrorBoundary`:
-```tsx host/src/RootErrorBoundary.tsx
-export function RootErrorBoundary() {
+```tsx host/src/ModuleErrorBoundary.tsx
+export function ModuleErrorBoundary() {
return (
An error occured while rendering a page from a module!
)
@@ -24,22 +24,22 @@ export function RootErrorBoundary() {
## Register the error boundary
-Then, update the host application `registerHost` function to declare the `RootErrorBoundary` component below the `RootLayout` component but above the routes of the modules. By doing so, if a module encounters an unhandled error, the error boundary will only replace the section rendered by the `Outlet` component within the root layout rather than the entire page.
+Then, update the host application `registerHost` function to declare the `ModuleErrorBoundary` component below the `RootLayout` component but above the routes of the modules. By doing so, if a module encounters an unhandled error, the error boundary will only replace the section rendered by the `Outlet` component within the root layout rather than the entire page.
A React Router's error boundary is declared with the [errorElement](https://reactrouter.com/en/main/route/error-element) of a route:
```tsx !#7,11 host/src/register.tsx
import { PublicRoutes, ProtectedRoutes, type ModuleRegisterFunction, type FireflyRuntime } from "@squide/firefly";
import { RootLayout } from "./RootLayout.tsx";
-import { RootErrorBoundary } from "./RootErrorBoundary.tsx";
+import { ModuleErrorBoundary } from "./ModuleErrorBoundary.tsx";
export const registerHost: ModuleRegisterFunction = runtime => {
runtime.registerRoute({
element: ,
children: [
{
- // Default error boundary.
- errorElement: ,
+ // Error boundary for modules.
+ errorElement: ,
children: [
PublicRoutes,
ProtectedRoutes
@@ -56,7 +56,7 @@ By implementing this mechanism, the level of failure isolation achieved is **com
### Hoisted pages
-If your application is [hoisting pages](../reference/runtime/runtime-class.md#register-an-hoisted-route), it's important to note that they will be rendered outside of the host application's `RootErrorBoundary` component. To prevent breaking the entire application when an hoisted page encounters unhandled errors, it is highly recommended to declare a React Router's error boundary for each hoisted page as well, again using [errorElement](https://reactrouter.com/en/main/route/error-element):
+If your application is [hoisting pages](../reference/runtime/runtime-class.md#register-an-hoisted-route), it's important to note that they will be rendered outside of the host application's `ModuleErrorBoundary` component. To prevent breaking the entire application when an hoisted page encounters unhandled errors, it is highly recommended to declare a React Router's error boundary for each hoisted page as well, again using [errorElement](https://reactrouter.com/en/main/route/error-element):
```tsx !#9,11 remote-module/src/register.tsx
import { type ModuleRegisterFunction, type FireflyRuntime } from "@squide/firefly";
@@ -76,7 +76,7 @@ export const register: ModuleRegisterFunction = runtime => {
## Try it :rocket:
-Start the application in a development environment using the `dev` script. Update any of your application routes that is rendered under the newly created error boundary (e.g. that is not hoisted) and throw an `Error`. The error should be handled by the `RootErrorBoundary` component instead of breaking the whole application.
+Start the application in a development environment using the `dev` script. Update any of your application routes that is rendered under the newly created error boundary (e.g. that is not hoisted) and throw an `Error`. The error should be handled by the `ModuleErrorBoundary` component instead of breaking the whole application.
### Troubleshoot issues
diff --git a/docs/guides/migrate-to-firefly-v9.md b/docs/guides/migrate-to-firefly-v9.md
index 2e3cc9df5..ef0ee1421 100644
--- a/docs/guides/migrate-to-firefly-v9.md
+++ b/docs/guides/migrate-to-firefly-v9.md
@@ -216,15 +216,11 @@ export function App() {
router={createBrowserRouter([
{
element: rootRoute,
+ errorElement: ,
children: [
{
- errorElement: ,
- children: [
- {
- element: ,
- children: registeredRoutes
- }
- ]
+ element: ,
+ children: registeredRoutes
}
]
}
@@ -342,7 +338,7 @@ export const registerHost: ModuleRegisterFunction = runtime => {
Now:
-```tsx !#12
+```tsx !#10
export function App() {
return (
@@ -352,12 +348,8 @@ export function App() {
router={createBrowserRouter([
{
element: rootRoute,
- children: [
- {
- errorElement: ,
- children: registeredRoutes
- }
- ]
+ errorElement: ,
+ children: registeredRoutes
}
])}
{...routerProviderProps}
@@ -375,3 +367,7 @@ The changes in `v9` have minimal impact on module code. To migrate an existing m
1. Convert all deferred routes into static routes. [View example](#removed-support-for-deferred-routes)
2. Add a `$key` option to the navigation item registrations. [View example](#new-key-option-for-navigation-items)
+
+### Isolated development
+
+If your module is set up for [isolated development](../guides/develop-a-module-in-isolation.md), ensure that you also apply the [host application migration steps](#migrate-an-host-application) to your isolated setup.
diff --git a/docs/reference/routing/appRouter.md b/docs/reference/routing/appRouter.md
index 9fece5682..84bf806c9 100644
--- a/docs/reference/routing/appRouter.md
+++ b/docs/reference/routing/appRouter.md
@@ -120,7 +120,7 @@ export function RootErrorBoundary() {
}
```
-```tsx !#16 host/src/App.tsx
+```tsx !#14 host/src/App.tsx
import { AppRouter } from "@squide/firefly";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { RootErrorBoundary } from "./RootErrorBoundary.tsx";
@@ -134,12 +134,8 @@ export function App() {
router={createBrowserRouter([
{
element: rootRoute,
- children: [
- {
- errorElement: ,
- children: registeredRoutes
- }
- ]
+ errorElement: ,
+ children: registeredRoutes
}
])}
{...routerProviderProps}
diff --git a/docs/reference/routing/protectedRoutes.md b/docs/reference/routing/protectedRoutes.md
index 2c4a7f2e6..bc84de749 100644
--- a/docs/reference/routing/protectedRoutes.md
+++ b/docs/reference/routing/protectedRoutes.md
@@ -24,22 +24,15 @@ None
The route defining the `ProtectedRoutes` placeholder must be [hoisted](../runtime/runtime-class.md#register-an-hoisted-route); otherwise, there will be an infinite loop as the `ProtectedRoutes` placeholder will render within itself.
-```tsx !#13,18 shell/src/register.tsx
+```tsx !#8,11 shell/src/register.tsx
import { ProtectedRoutes } from "@squide/firefly";
import { RootLayout } from "./RootLayout.tsx";
-import { RootErrorBoundary } from "./RootErrorBoundary.tsx";
runtime.registerRoute({
// Pathless route to declare a root layout.
element: ,
children: [
- {
- // Pathless route to declare a root error boundary.
- errorElement: ,
- children: [
- ProtectedRoutes
- ]
- }
+ ProtectedRoutes
]
}, {
hoist: true
diff --git a/docs/reference/routing/publicRoutes.md b/docs/reference/routing/publicRoutes.md
index 1b58aaee0..58a60e2ea 100644
--- a/docs/reference/routing/publicRoutes.md
+++ b/docs/reference/routing/publicRoutes.md
@@ -24,22 +24,15 @@ None
The route defining the `PublicRoutes` placeholder must be [hoisted](../runtime/runtime-class.md#register-an-hoisted-route); otherwise, there will be an infinite loop as the `PublicRoutes` placeholder will render within itself.
-```tsx !#13,18 shell/src/register.tsx
+```tsx !#8,11 shell/src/register.tsx
import { PublicRoutes } from "@squide/firefly";
import { RootLayout } from "./RootLayout.tsx";
-import { RootErrorBoundary } from "./RootErrorBoundary.tsx";
runtime.registerRoute({
// Pathless route to declare a root layout.
element: ,
children: [
- {
- // Pathless route to declare a root error boundary.
- errorElement: ,
- children: [
- PublicRoutes
- ]
- }
+ PublicRoutes
]
}, {
hoist: true
diff --git a/docs/reference/tanstack-query/useProtectedDataQueries.md b/docs/reference/tanstack-query/useProtectedDataQueries.md
index cd37afebc..3c2078023 100644
--- a/docs/reference/tanstack-query/useProtectedDataQueries.md
+++ b/docs/reference/tanstack-query/useProtectedDataQueries.md
@@ -188,7 +188,7 @@ export function RootErrorBoundary() {
}
```
-```tsx !#58 host/src/App.tsx
+```tsx !#55 host/src/App.tsx
import { useProtectedDataQueries, useIsBootstrapping, AppRouter } from "@squide/firefly";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { ApiError, SessionContext, type Session } from "@sample/shared";
@@ -243,10 +243,10 @@ export function App() {
router={createBrowserRouter([
{
element: rootRoute,
+ errorElement: ,
children: [
{
element: ,
- errorElement:
children: registeredRoutes
}
]
diff --git a/docs/reference/tanstack-query/usePublicDataQueries.md b/docs/reference/tanstack-query/usePublicDataQueries.md
index a3d0a3484..614793191 100644
--- a/docs/reference/tanstack-query/usePublicDataQueries.md
+++ b/docs/reference/tanstack-query/usePublicDataQueries.md
@@ -159,7 +159,7 @@ export function RootErrorBoundary() {
}
```
-```tsx !#48 host/src/App.tsx
+```tsx !#45 host/src/App.tsx
import { usePublicDataQueries, useIsBootstrapping, AppRouter } from "@squide/firefly";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { ApiError, FeatureFlagsContext, type FeatureFlags } from "@sample/shared";
@@ -204,10 +204,10 @@ export function App() {
router={createBrowserRouter([
{
element: rootRoute,
+ errorElement: ,
children: [
{
element: ,
- errorElement:
children: registeredRoutes
}
]
diff --git a/packages/firefly/src/useProtectedDataQueries.ts b/packages/firefly/src/useProtectedDataQueries.ts
index 59add693d..45e8aba69 100644
--- a/packages/firefly/src/useProtectedDataQueries.ts
+++ b/packages/firefly/src/useProtectedDataQueries.ts
@@ -23,9 +23,9 @@ export function useProtectedDataQueries>(queries: QueriesOp
data: results.map(x => x.data) as MapUseQueryResultToData>,
errors,
hasErrors: errors.length > 0,
- isReady: results.length === queries.length && results.every(x => x.data)
+ isReady: !results.some(x => x.isPending)
};
- }, [queries.length]);
+ }, []);
const { data, errors: queriesErrors, hasErrors, isReady } = useQueries({
queries: queries.map(x => ({
diff --git a/samples/basic/shell/src/AppRouter.tsx b/samples/basic/shell/src/AppRouter.tsx
index c27867542..95698ab2b 100644
--- a/samples/basic/shell/src/AppRouter.tsx
+++ b/samples/basic/shell/src/AppRouter.tsx
@@ -1,5 +1,5 @@
import { SessionManagerContext, useToastListener } from "@basic/shared";
-import { AppRouter as FireflyAppRouter, useIsBootstrapping } from "@squide/firefly";
+import { AppRouter as FireflyAppRouter, useIsBootstrapping, useLogger } from "@squide/firefly";
import { useCallback } from "react";
import { Outlet, RouterProvider, createBrowserRouter } from "react-router-dom";
import { Loading } from "./Loading.tsx";
@@ -32,18 +32,22 @@ function BootstrappingRoute() {
}
export function AppRouter() {
+ const logger = useLogger();
+
return (
{({ rootRoute, registeredRoutes, routerProviderProps }) => {
+ logger.debug("[shell] React Router will be rendered with the following route definitions: ", registeredRoutes);
+
return (
,
children: [
{
element: ,
- errorElement: ,
children: registeredRoutes
}
]
diff --git a/samples/endpoints/shell/src/AppRouter.tsx b/samples/endpoints/shell/src/AppRouter.tsx
index 41a671b46..985d79f21 100644
--- a/samples/endpoints/shell/src/AppRouter.tsx
+++ b/samples/endpoints/shell/src/AppRouter.tsx
@@ -106,6 +106,8 @@ export interface AppRouterProps {
}
export function AppRouter(props: AppRouterProps) {
+ const logger = useLogger();
+
const {
waitForMsw,
telemetryService
@@ -114,15 +116,17 @@ export function AppRouter(props: AppRouterProps) {
return (
{({ rootRoute, registeredRoutes, routerProviderProps }) => {
+ logger.debug("[shell] React Router will be rendered with the following route definitions: ", registeredRoutes);
+
return (
,
children: [
{
element: ,
- errorElement: ,
children: registeredRoutes
}
]
diff --git a/templates/getting-started/apps/host/src/NotFoundPage.tsx b/templates/getting-started/apps/host/src/NotFoundPage.tsx
new file mode 100644
index 000000000..bca1b04cd
--- /dev/null
+++ b/templates/getting-started/apps/host/src/NotFoundPage.tsx
@@ -0,0 +1,5 @@
+export function NotFoundPage() {
+ return (
+ Not found! Please try another page.
+ );
+}
diff --git a/templates/getting-started/apps/host/src/register.tsx b/templates/getting-started/apps/host/src/register.tsx
index 2f38b3875..8efd3abd3 100644
--- a/templates/getting-started/apps/host/src/register.tsx
+++ b/templates/getting-started/apps/host/src/register.tsx
@@ -1,5 +1,6 @@
import { ProtectedRoutes, PublicRoutes, type FireflyRuntime, type ModuleRegisterFunction } from "@squide/firefly";
import { HomePage } from "./HomePage.tsx";
+import { NotFoundPage } from "./NotFoundPage.tsx";
import { RootLayout } from "./RootLayout.tsx";
export const registerHost: ModuleRegisterFunction = runtime => {
@@ -16,6 +17,11 @@ export const registerHost: ModuleRegisterFunction = runtime => {
hoist: true
});
+ runtime.registerPublicRoute({
+ path: "*",
+ element:
+ });
+
runtime.registerRoute({
index: true,
element: