Skip to content

Commit c2c74a3

Browse files
lulucatdevmrdotb
authored andcommitted
feat: Add React Link component for Phoenix LiveView navigation
1 parent 75c1c71 commit c2c74a3

20 files changed

Lines changed: 1158 additions & 551 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ React inside Phoenix LiveView.
1717
- 💀 **Dead View** Support
1818
- 🐌 **Lazy-loading** React Components
1919
- 🦥 **Slot** Interoperability
20+
- 🔗 **Link Component** for LiveView Navigation
2021
- 🚀 **Amazing DX** with Vite
2122

2223
## Resources
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Simple } from "./simple";
2+
import { LinkExample } from "./link-example";
3+
import { Link } from "live_react";
24

35
export default {
46
Simple,
7+
LinkExample,
8+
Link,
59
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from "react";
2+
import { Link } from "live_react";
3+
4+
export function LinkExample({}) {
5+
return (
6+
<div className="space-y-4 p-4">
7+
<h2 className="text-xl font-bold">Link Component Examples</h2>
8+
9+
<div className="space-y-2">
10+
<p>
11+
<Link href="/external" className="text-blue-600 hover:underline">
12+
External Link (full page reload)
13+
</Link>
14+
</p>
15+
16+
<p>
17+
<Link patch="/same-liveview" className="text-green-600 hover:underline">
18+
Patch Link (same LiveView, calls handle_params)
19+
</Link>
20+
</p>
21+
22+
<p>
23+
<Link navigate="/other-liveview" className="text-purple-600 hover:underline">
24+
Navigate Link (different LiveView, same session)
25+
</Link>
26+
</p>
27+
28+
<p>
29+
<Link
30+
navigate="/replace-history"
31+
replace={true}
32+
className="text-red-600 hover:underline"
33+
>
34+
Replace History (navigate with replace)
35+
</Link>
36+
</p>
37+
</div>
38+
</div>
39+
);
40+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Link } from "live_react";
2+
3+
// Re-export Link component for direct usage
4+
export { Link };

assets/js/live_react/index.d.mts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React from "react";
2+
13
export interface LiveProps {
24
pushEvent: (
35
event: string,
@@ -19,4 +21,17 @@ export interface LiveProps {
1921
uploadTo: (target: string, name: string, files: FileList | File[]) => void;
2022
}
2123

24+
export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
25+
/** Uses traditional browser navigation to the new location. This means the whole page is reloaded. */
26+
href?: string | null;
27+
/** Patches the current LiveView. The handle_params callback will be invoked with minimum content sent over the wire. */
28+
patch?: string | null;
29+
/** Navigates to a LiveView. Only works between LiveViews in the same live_session. */
30+
navigate?: string | null;
31+
/** When using patch or navigate, should the browser's history be replaced with pushState? */
32+
replace?: boolean;
33+
children?: React.ReactNode;
34+
}
35+
2236
export function useLiveReact(): LiveProps;
37+
export function Link(props: LinkProps): React.ReactElement;

assets/js/live_react/index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { getHooks } from "./hooks";
22
export { useLiveReact } from "./context";
3+
export { Link } from "./link.jsx";

assets/js/live_react/link.jsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { useMemo } from "react";
2+
3+
/**
4+
* Phoenix LiveView Link component for React
5+
*
6+
* Handles different types of navigation in Phoenix LiveView:
7+
* - href: Traditional browser navigation (full page reload)
8+
* - patch: Patches the current LiveView (calls handle_params)
9+
* - navigate: Navigates to a different LiveView within the same live_session
10+
* - replace: Whether to replace or push browser history
11+
*/
12+
export function Link({
13+
href = null,
14+
patch = null,
15+
navigate = null,
16+
replace = false,
17+
children,
18+
...attrs
19+
}) {
20+
const linkAttrs = useMemo(() => {
21+
if (!patch && !navigate) {
22+
return {
23+
href: href || "#",
24+
};
25+
}
26+
27+
return {
28+
href: (navigate ? navigate : patch) || "#",
29+
"data-phx-link": navigate ? "redirect" : "patch",
30+
"data-phx-link-state": replace ? "replace" : "push",
31+
};
32+
}, [href, patch, navigate, replace]);
33+
34+
return (
35+
<a {...attrs} {...linkAttrs}>
36+
{children}
37+
</a>
38+
);
39+
}

guides/development.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ The easiest way to get started with development is to clone live_react and run t
66
git clone https://github.com/mrdotb/live_react.git
77
cd live_react_examples
88
```
9+
10+
The examples include demonstrations of the Link component for LiveView navigation at `/link-demo` and `/link-usage`.

guides/installation.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,32 @@ children = [
178178
<.react name="Simple" />
179179
```
180180

181+
You can also use the built-in Link component for LiveView navigation:
182+
183+
```elixir
184+
<!-- Use Link component directly in templates -->
185+
<.react name="Link" href="/some-page">External Link</.react>
186+
<.react name="Link" patch="/current-liveview?tab=new">Patch Link</.react>
187+
<.react name="Link" navigate="/other-liveview">Navigate Link</.react>
188+
189+
<!-- Or import it in your React components -->
190+
```
191+
192+
```javascript
193+
import { Link } from "live_react";
194+
195+
function MyComponent() {
196+
return (
197+
<div>
198+
<Link href="/external">Traditional Link</Link>
199+
<Link patch="/same-lv?param=value">Patch Current LiveView</Link>
200+
<Link navigate="/other-lv">Navigate to Other LiveView</Link>
201+
<Link navigate="/replace" replace={true}>Replace History</Link>
202+
</div>
203+
);
204+
}
205+
```
206+
181207
14. (Optional) enable [stateful hot reload](https://twitter.com/jskalc/status/1788308446007132509) of phoenix LiveViews - it allows for stateful reload across the whole stack 🤯. Just adjust your `dev.exs` to look like this - add `notify` section and remove `live|components` from patterns.
182208

183209
```elixir

0 commit comments

Comments
 (0)