Skip to content

Commit

Permalink
fix: Deferred routes with protected data are now working as expected …
Browse files Browse the repository at this point in the history
…and a default catch has been added to AppRouter for AbortSignal errors. (#133)

* Fixed deferred routes with protected data

* Added changeset files
  • Loading branch information
patricklafrance authored Jan 17, 2024
1 parent b9925c8 commit 1cda1be
Show file tree
Hide file tree
Showing 19 changed files with 171 additions and 202 deletions.
7 changes: 7 additions & 0 deletions .changeset/fuzzy-pandas-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@squide/firefly": patch
---

- To prevent the consumer from always handling the AbortSignal error, a default catch has been added to the `AppRouter` component. The consumer can still handler the AbortSignal error if he needs to.

- When a user makes a direct hit to a deferred route that depends on protected data, the protected data was undefined. The reason was that by default, an unregistered route was considered as a public route. The code has been updated to consider an uregistered route as a protected route. The upside is that deferred routes can now depends on protected data. The downside is that a public deferred route will trigger the loading of the protected data. As we don't expect to have public deferred route at the moment it doesn't seems like an issue.
5 changes: 5 additions & 0 deletions .changeset/hungry-peaches-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@squide/webpack-module-federation": patch
---

The completeDeferredRegistrations function data was required instead of optionnal. It has been fixed.
5 changes: 5 additions & 0 deletions .changeset/sharp-rocks-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@squide/react-router": patch
---

The `useIsRouteMatchProtected` hook now returns `true` when the location match an unregistered route.
6 changes: 6 additions & 0 deletions .changeset/smart-seas-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@squide/core": patch
---

The completeDeferredRegistrations function data was required instead of optionnal. It has been fixed.

2 changes: 1 addition & 1 deletion docs/getting-started/create-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function App() {

Next, create a layout component to [render the navigation items](/reference/routing/useRenderedNavigationItems.md). In many applications, multiple pages often share a **common layout** that includes elements such as a navigation bar, a user profile menu, and a main content section. In a [React Router](https://reactrouter.com/en/main) application, this shared layout is commonly referred to as a `RootLayout`:

```tsx !#38,41 host/src/RootLayout.tsx
```tsx !#40,43 host/src/RootLayout.tsx
import { Suspense } from "react";
import { Link, Outlet } from "react-router-dom";
import {
Expand Down
30 changes: 12 additions & 18 deletions docs/guides/add-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,34 +309,28 @@ export const requestHandlers: HttpHandler[] = [

Then, update the host application `App` component to load the session when a user navigate to a protected page for the first time:

```tsx !#20,22,31,33-35,39-40 host/src/App.tsx
```tsx !#19,21,25,27-29,33-34 host/src/App.tsx
import { AppRouter } from "@squide/firefly";
import type { Session } from "@sample/shared";
import { sessionManager } from "./session.ts";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

async function fetchProtectedData(setIsSessionLoaded: (isLoaded: boolean) => void,signal: AbortSignal) {
try {
const response = await fetch("/api/session", {
signal
});
const response = await fetch("/api/session", {
signal
});

const data = await response.json();
const data = await response.json();

const session: Session = {
user: {
name: data.username
}
};
const session: Session = {
user: {
name: data.username
}
};

sessionManager.setSession(session);
sessionManager.setSession(session);

setIsSessionLoaded(true);
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
}
setIsSessionLoaded(true);
}

export function App() {
Expand Down
72 changes: 27 additions & 45 deletions docs/guides/fetch-initial-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,26 +88,20 @@ Ensure that the shared project is configured as a [shared dependency](./add-a-sh

Finally, open the host application code and update the `App` component to utilize the `AppRouter` component's [onLoadPublicData](../reference/routing/appRouter.md#load-public-data) handler. This handler will fetch the count and forward the retrieved value through `FetchCountContext`:

```tsx !#9,16-18,23,25-27,30,32,33 host/src/App.tsx
```tsx !#8,17,19-21,24,26-27 host/src/App.tsx
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) {
try {
const response = await fetch("/api/count", {
signal
});

const data = await response.json();

setFetchCount(data.count);
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
}
const response = await fetch("/api/count", {
signal
});

const data = await response.json();

setFetchCount(data.count);
}

export function App() {
Expand Down Expand Up @@ -289,46 +283,34 @@ Ensure that the shared project is configured as a [shared dependency](./add-a-sh

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`:

```tsx !#25,36-38,44,50-52,56,59,61 host/src/App.tsx
```tsx !#18,32,38-40,44,47,49 host/src/App.tsx
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) {
try {
const response = await fetch("/api/count", {
signal
});

const data = await response.json();

setFetchCount(data.count);
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
}
const response = await fetch("/api/count", {
signal
});

const data = await response.json();

setFetchCount(data.count);
}

async function fetchProtectedData(setSubscription: (subscription: Subscription) => void, signal: AbortSignal) {
try {
const response = await fetch("/api/subscription", {
signal
});

const data = await response.json();

const subscription: Subscription = {
status: data.status
};

setSubscription(subscription);
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
}
const response = await fetch("/api/subscription", {
signal
});

const data = await response.json();

const subscription: Subscription = {
status: data.status
};

setSubscription(subscription);
}

export function App() {
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/override-a-react-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ In the previous code samples, the host application provides a value for the `Bac

Now, suppose the requirements change, and one remote module pages need to have a `red` background. The context can be overriden for the remote module by declaring a new provider directly in the routes registration:

```tsx !#9,11 remote-module/src/register.tsx
```tsx !#9 remote-module/src/register.tsx
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { BackgroundColorContext } from "@sample/shared";
import { ColoredPage } from "./ColoredPage.tsx";
Expand Down
32 changes: 13 additions & 19 deletions docs/guides/setup-i18next.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,32 +422,26 @@ Hence, the strategy to select the displayed language should be as follow:

To implement this strategy, use the [useChangeLanguage](../reference/i18next/useChangeLanguage.md) hook and the [onLoadProtectedData](../reference/routing/appRouter.md#load-protected-data) handler of the [AppRouter](../reference/routing/appRouter.md) component:

```tsx !#18,35,37-39,46-47 host/src/App.tsx
```tsx !#17,29,31-33,40-41 host/src/App.tsx
import { AppRouter } from "@squide/firefly";
import { useChangeLanguage, useI18nextInstance } from "@squide/i18next";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

async function fetchProtectedData(changeLanguage: (language: string) => void, setIsSessionLoaded: (isLoaded: boolean) => void, signal: AbortSignal) {
try {
const response = await fetch("/api/session", {
signal
});

if (response.ok) {
const session = await response.json();

// When the session has been retrieved, change the displayed language to match
// the preferred language setting.
changeLanguage(session.preferredLanguage);

setIsSessionLoaded(true);
}
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
const response = await fetch("/api/session", {
signal
});

if (response.ok) {
const session = await response.json();

// When the session has been retrieved, change the displayed language to match
// the preferred language setting.
changeLanguage(session.preferredLanguage);

setIsSessionLoaded(true);
}
}

Expand Down
52 changes: 20 additions & 32 deletions docs/guides/use-feature-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,31 +81,25 @@ Ensure that the shared project is configured as a [shared dependency](./add-a-sh

Finally, open the host application code and update the `App` component to utilize the `AppRouter` component's `onLoadPublicData` handler to fetch the feature flags data:

```tsx !#30-32,35,37-38 host/src/App.tsx
```tsx !#24-26,29,31-32 host/src/App.tsx
import { useState, useCallback } from "react";
import { AppRouter } from "@squide/firefly";
import { FeatureFlagsContext, type FeatureFlags } from "@sample/shared";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

async function fetchPublicData(setFeatureFlags: (featureFlags: FeatureFlags) => void, signal: AbortSignal) {
try {
const response = await fetch("/api/feature-flags", {
signal
});
const response = await fetch("/api/feature-flags", {
signal
});

const data = await response.json();
const data = await response.json();

const featureFlags: FeatureFlags = {
featureA: data.featureA,
featureB: data.featureB
};
const featureFlags: FeatureFlags = {
featureA: data.featureA,
featureB: data.featureB
};

setFeatureFlags(featureFlags);
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
}
setFeatureFlags(featureFlags);
}

export function App() {
Expand Down Expand Up @@ -210,28 +204,22 @@ export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredR
Finally, open the host application code again and update the `App` component to utilize the `AppRouter` component's `onCompleteRegistrations` handler to [complete the module registrations](../reference/registration/completeRemoteModuleRegistrations.md) with the feature flags:
```tsx !#33-38,45 host/src/App.tsx
```tsx !#27-32,39 host/src/App.tsx
import { useState, useCallback } from "react";
import { AppRouter, useRuntime, completeModuleRegistrations } from "@squide/firefly";
import { FeatureFlagsContext, type FeatureFlags } from "@sample/shared";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

async function fetchPublicData(setFeatureFlags: (featureFlags: FeatureFlags) => void, signal: AbortSignal) {
try {
const response = await fetch("/api/feature-flags");
const data = await response.json();

const featureFlags: FeatureFlags = {
featureA: data.featureA,
featureB: data.featureB
};

setFeatureFlags(featureFlags);
} catch (error: unknown) {
if (!signal.aborted) {
throw error;
}
}
const response = await fetch("/api/feature-flags");
const data = await response.json();

const featureFlags: FeatureFlags = {
featureA: data.featureA,
featureB: data.featureB
};

setFeatureFlags(featureFlags);
}

export function App() {
Expand Down
Loading

0 comments on commit 1cda1be

Please sign in to comment.