Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/fep-2677-link-url-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@stackflow/link": major
---

Require Link consumers to provide a URL resolver through
`LinkUrlResolverProvider`. This removes Link's direct dependency on
`@stackflow/plugin-history-sync` and keeps generated URLs consistent with the
configured routing plugin.
136 changes: 40 additions & 96 deletions .pnp.cjs

Large diffs are not rendered by default.

27 changes: 20 additions & 7 deletions extensions/link/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ It mimics the `<Link />` component behavior provided by Gatsby or Next.js.

## Dependencies

It can be used only when `@stackflow/plugin-history-sync` is set.

- `@stackflow/plugin-history-sync`
Provide a URL resolver with `LinkUrlResolverProvider`. The resolver can come
from `@stackflow/plugin-history-sync` or another routing plugin.

## Usage

Expand Down Expand Up @@ -38,21 +37,35 @@ import { historySyncPlugin } from "@stackflow/plugin-history-sync";
import { config } from "./stackflow.config";
import { MyActivity } from "./MyActivity";

const historySync = historySyncPlugin({
config,
fallbackActivity: () => "MyActivity",
});

const { Stack } = stackflow({
config,
components: {
MyActivity,
},
plugins: [
historySyncPlugin({
config,
fallbackActivity: () => "MyActivity",
}),
historySync,
// ...
],
});
```

Wrap `Stack` with the resolver from the routing plugin.

```tsx
import { LinkUrlResolverProvider } from "@stackflow/link";

const App = () => (
<LinkUrlResolverProvider resolver={historySync.urlResolver}>
<Stack />
</LinkUrlResolverProvider>
);
```

```tsx
/**
* MyComponent.ts
Expand Down
2 changes: 0 additions & 2 deletions extensions/link/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"@stackflow/config": "^2.0.0",
"@stackflow/core": "^3.0.0",
"@stackflow/esbuild-config": "^1.0.3",
"@stackflow/plugin-history-sync": "^2.1.0",
"@stackflow/react": "^2.1.2",
"@types/react": "^18.3.3",
"esbuild": "^0.23.0",
Expand All @@ -45,7 +44,6 @@
},
"peerDependencies": {
"@stackflow/core": "^2.0.0 || ^3.0.0",
"@stackflow/plugin-history-sync": "^1.6.4-canary.0 || ^2.0.0",
"@stackflow/react": "^2.0.0",
"@types/react": ">=16.8.0",
"react": ">=16.8.0"
Expand Down
33 changes: 7 additions & 26 deletions extensions/link/src/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
/// <reference types="@stackflow/plugin-history-sync" />

import type {
InferActivityParams,
RegisteredActivityName,
} from "@stackflow/config";
import type { Route } from "@stackflow/plugin-history-sync";
import { useConfig, useFlow } from "@stackflow/react";
import { useFlow } from "@stackflow/react";
import { useMemo } from "react";
import { useLinkUrlResolver } from "./LinkUrlResolverContext";
import { omit } from "./omit";

function toRoute<T>(route: string | Route<T>): Route<T> {
return typeof route === "string" ? { path: route, decode: undefined } : route;
}

type AnchorProps = Omit<
React.DetailedHTMLProps<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
Expand All @@ -31,26 +25,13 @@ export interface LinkProps<K extends RegisteredActivityName>
}

export function Link<K extends RegisteredActivityName>(props: LinkProps<K>) {
const config = useConfig();
const urlResolver = useLinkUrlResolver();
const { push, replace } = useFlow();

const href = useMemo(() => {
const match = config.activities.find((r) => r.name === props.activityName);

if (!match || !match.route || !config.historySync) {
return undefined;
}

const { path, decode } = Array.isArray(match.route)
? toRoute(match.route[0])
: toRoute(match.route);

const { makeTemplate, urlPatternOptions } = config.historySync;

const template = makeTemplate({ path, decode }, urlPatternOptions);

return template.fill(props.activityParams);
}, [config, props.activityName, props.activityParams]);
const href = useMemo(
() => urlResolver.makeActivityUrl(props.activityName, props.activityParams),
[urlResolver.makeActivityUrl, props.activityName, props.activityParams],
);

const anchorProps = omit(props, [
// Custom Props
Expand Down
38 changes: 38 additions & 0 deletions extensions/link/src/LinkUrlResolverContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createContext, type ReactNode, useContext } from "react";

export interface LinkUrlResolver {
readonly makeActivityUrl: (
activityName: string,
activityParams: Record<string, any>,
) => string;
}

export const LinkUrlResolverContext = createContext<LinkUrlResolver | null>(
null,
);

export function LinkUrlResolverProvider({
resolver,
children,
}: {
resolver: LinkUrlResolver;
children: ReactNode;
}) {
return (
<LinkUrlResolverContext.Provider value={resolver}>
{children}
</LinkUrlResolverContext.Provider>
);
}

export function useLinkUrlResolver() {
const urlResolver = useContext(LinkUrlResolverContext);

if (urlResolver === null) {
throw new Error(
"No LinkUrlResolver was found in context. Wrap the component tree with LinkUrlResolverProvider.",
);
}

return urlResolver;
}
4 changes: 4 additions & 0 deletions extensions/link/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export * from "./Link";
export {
type LinkUrlResolver,
LinkUrlResolverProvider,
} from "./LinkUrlResolverContext";
4 changes: 1 addition & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5728,7 +5728,6 @@ __metadata:
"@stackflow/config": "npm:^2.0.0"
"@stackflow/core": "npm:^3.0.0"
"@stackflow/esbuild-config": "npm:^1.0.3"
"@stackflow/plugin-history-sync": "npm:^2.1.0"
"@stackflow/react": "npm:^2.1.2"
"@types/react": "npm:^18.3.3"
esbuild: "npm:^0.23.0"
Expand All @@ -5738,7 +5737,6 @@ __metadata:
typescript: "npm:^5.5.3"
peerDependencies:
"@stackflow/core": ^2.0.0 || ^3.0.0
"@stackflow/plugin-history-sync": ^1.6.4-canary.0 || ^2.0.0
"@stackflow/react": ^2.0.0
"@types/react": ">=16.8.0"
react: ">=16.8.0"
Expand Down Expand Up @@ -5890,7 +5888,7 @@ __metadata:
languageName: node
linkType: hard

"@stackflow/plugin-history-sync@npm:^2.1.0, @stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync":
"@stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync":
version: 0.0.0-use.local
resolution: "@stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync"
dependencies:
Expand Down
Loading