Skip to content

Commit 8566b5e

Browse files
feat(link)!: require URL resolver injection (#751)
1 parent 6cbc7a2 commit 8566b5e

8 files changed

Lines changed: 118 additions & 134 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@stackflow/link": major
3+
---
4+
5+
Require Link consumers to provide a URL resolver through
6+
`LinkUrlResolverProvider`. This removes Link's direct dependency on
7+
`@stackflow/plugin-history-sync` and keeps generated URLs consistent with the
8+
configured routing plugin.

.pnp.cjs

Lines changed: 40 additions & 96 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/link/README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ It mimics the `<Link />` component behavior provided by Gatsby or Next.js.
44

55
## Dependencies
66

7-
It can be used only when `@stackflow/plugin-history-sync` is set.
8-
9-
- `@stackflow/plugin-history-sync`
7+
Provide a URL resolver with `LinkUrlResolverProvider`. The resolver can come
8+
from `@stackflow/plugin-history-sync` or another routing plugin.
109

1110
## Usage
1211

@@ -38,21 +37,35 @@ import { historySyncPlugin } from "@stackflow/plugin-history-sync";
3837
import { config } from "./stackflow.config";
3938
import { MyActivity } from "./MyActivity";
4039

40+
const historySync = historySyncPlugin({
41+
config,
42+
fallbackActivity: () => "MyActivity",
43+
});
44+
4145
const { Stack } = stackflow({
4246
config,
4347
components: {
4448
MyActivity,
4549
},
4650
plugins: [
47-
historySyncPlugin({
48-
config,
49-
fallbackActivity: () => "MyActivity",
50-
}),
51+
historySync,
5152
// ...
5253
],
5354
});
5455
```
5556

57+
Wrap `Stack` with the resolver from the routing plugin.
58+
59+
```tsx
60+
import { LinkUrlResolverProvider } from "@stackflow/link";
61+
62+
const App = () => (
63+
<LinkUrlResolverProvider resolver={historySync.urlResolver}>
64+
<Stack />
65+
</LinkUrlResolverProvider>
66+
);
67+
```
68+
5669
```tsx
5770
/**
5871
* MyComponent.ts

extensions/link/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"@stackflow/config": "^2.0.0",
3535
"@stackflow/core": "^3.0.0",
3636
"@stackflow/esbuild-config": "^1.0.3",
37-
"@stackflow/plugin-history-sync": "^2.1.0",
3837
"@stackflow/react": "^2.1.2",
3938
"@types/react": "^18.3.3",
4039
"esbuild": "^0.23.0",
@@ -45,7 +44,6 @@
4544
},
4645
"peerDependencies": {
4746
"@stackflow/core": "^2.0.0 || ^3.0.0",
48-
"@stackflow/plugin-history-sync": "^1.6.4-canary.0 || ^2.0.0",
4947
"@stackflow/react": "^2.0.0",
5048
"@types/react": ">=16.8.0",
5149
"react": ">=16.8.0"

extensions/link/src/Link.tsx

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
/// <reference types="@stackflow/plugin-history-sync" />
2-
31
import type {
42
InferActivityParams,
53
RegisteredActivityName,
64
} from "@stackflow/config";
7-
import type { Route } from "@stackflow/plugin-history-sync";
8-
import { useConfig, useFlow } from "@stackflow/react";
5+
import { useFlow } from "@stackflow/react";
96
import { useMemo } from "react";
7+
import { useLinkUrlResolver } from "./LinkUrlResolverContext";
108
import { omit } from "./omit";
119

12-
function toRoute<T>(route: string | Route<T>): Route<T> {
13-
return typeof route === "string" ? { path: route, decode: undefined } : route;
14-
}
15-
1610
type AnchorProps = Omit<
1711
React.DetailedHTMLProps<
1812
React.AnchorHTMLAttributes<HTMLAnchorElement>,
@@ -31,26 +25,13 @@ export interface LinkProps<K extends RegisteredActivityName>
3125
}
3226

3327
export function Link<K extends RegisteredActivityName>(props: LinkProps<K>) {
34-
const config = useConfig();
28+
const urlResolver = useLinkUrlResolver();
3529
const { push, replace } = useFlow();
3630

37-
const href = useMemo(() => {
38-
const match = config.activities.find((r) => r.name === props.activityName);
39-
40-
if (!match || !match.route || !config.historySync) {
41-
return undefined;
42-
}
43-
44-
const { path, decode } = Array.isArray(match.route)
45-
? toRoute(match.route[0])
46-
: toRoute(match.route);
47-
48-
const { makeTemplate, urlPatternOptions } = config.historySync;
49-
50-
const template = makeTemplate({ path, decode }, urlPatternOptions);
51-
52-
return template.fill(props.activityParams);
53-
}, [config, props.activityName, props.activityParams]);
31+
const href = useMemo(
32+
() => urlResolver.makeActivityUrl(props.activityName, props.activityParams),
33+
[urlResolver.makeActivityUrl, props.activityName, props.activityParams],
34+
);
5435

5536
const anchorProps = omit(props, [
5637
// Custom Props
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { createContext, type ReactNode, useContext } from "react";
2+
3+
export interface LinkUrlResolver {
4+
readonly makeActivityUrl: (
5+
activityName: string,
6+
activityParams: Record<string, any>,
7+
) => string;
8+
}
9+
10+
export const LinkUrlResolverContext = createContext<LinkUrlResolver | null>(
11+
null,
12+
);
13+
14+
export function LinkUrlResolverProvider({
15+
resolver,
16+
children,
17+
}: {
18+
resolver: LinkUrlResolver;
19+
children: ReactNode;
20+
}) {
21+
return (
22+
<LinkUrlResolverContext.Provider value={resolver}>
23+
{children}
24+
</LinkUrlResolverContext.Provider>
25+
);
26+
}
27+
28+
export function useLinkUrlResolver() {
29+
const urlResolver = useContext(LinkUrlResolverContext);
30+
31+
if (urlResolver === null) {
32+
throw new Error(
33+
"No LinkUrlResolver was found in context. Wrap the component tree with LinkUrlResolverProvider.",
34+
);
35+
}
36+
37+
return urlResolver;
38+
}

extensions/link/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
export * from "./Link";
2+
export {
3+
type LinkUrlResolver,
4+
LinkUrlResolverProvider,
5+
} from "./LinkUrlResolverContext";

yarn.lock

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5728,7 +5728,6 @@ __metadata:
57285728
"@stackflow/config": "npm:^2.0.0"
57295729
"@stackflow/core": "npm:^3.0.0"
57305730
"@stackflow/esbuild-config": "npm:^1.0.3"
5731-
"@stackflow/plugin-history-sync": "npm:^2.1.0"
57325731
"@stackflow/react": "npm:^2.1.2"
57335732
"@types/react": "npm:^18.3.3"
57345733
esbuild: "npm:^0.23.0"
@@ -5738,7 +5737,6 @@ __metadata:
57385737
typescript: "npm:^5.5.3"
57395738
peerDependencies:
57405739
"@stackflow/core": ^2.0.0 || ^3.0.0
5741-
"@stackflow/plugin-history-sync": ^1.6.4-canary.0 || ^2.0.0
57425740
"@stackflow/react": ^2.0.0
57435741
"@types/react": ">=16.8.0"
57445742
react: ">=16.8.0"
@@ -5890,7 +5888,7 @@ __metadata:
58905888
languageName: node
58915889
linkType: hard
58925890

5893-
"@stackflow/plugin-history-sync@npm:^2.1.0, @stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync":
5891+
"@stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync":
58945892
version: 0.0.0-use.local
58955893
resolution: "@stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync"
58965894
dependencies:

0 commit comments

Comments
 (0)