Skip to content

Commit 231831f

Browse files
committed
feat: create context provider
1 parent 26a65bd commit 231831f

13 files changed

Lines changed: 148 additions & 35 deletions

File tree

assets/js/live_react/context.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React, { createContext, useContext } from "react";
2+
3+
export const LiveReactContext = createContext(null);
4+
5+
export function LiveReactProvider({ children, ...props }) {
6+
return (
7+
<LiveReactContext.Provider value={props}>
8+
{children}
9+
</LiveReactContext.Provider>
10+
);
11+
}
12+
13+
export function useLiveReact() {
14+
return useContext(LiveReactContext);
15+
}

assets/js/live_react/hooks.js

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import ReactDOM from "react-dom/client";
3+
import { getComponentTree } from "./utils";
34

45
function getAttributeJson(el, attributeName) {
56
const data = el.getAttribute(attributeName);
@@ -23,7 +24,6 @@ function getChildren(hook) {
2324
function getProps(hook) {
2425
return {
2526
...getAttributeJson(hook.el, "data-props"),
26-
// pass the hook callbacks to the component
2727
pushEvent: hook.pushEvent.bind(hook),
2828
pushEventTo: hook.pushEventTo.bind(hook),
2929
handleEvent: hook.handleEvent.bind(hook),
@@ -36,13 +36,12 @@ function getProps(hook) {
3636
export function getHooks(components) {
3737
const ReactHook = {
3838
_render() {
39-
this._root.render(
40-
React.createElement(
41-
this._Component,
42-
getProps(this),
43-
...getChildren(this),
44-
),
39+
const tree = getComponentTree(
40+
this._Component,
41+
getProps(this),
42+
getChildren(this),
4543
);
44+
this._root.render(tree);
4645
},
4746
mounted() {
4847
const componentName = this.el.getAttribute("data-name");
@@ -55,14 +54,12 @@ export function getHooks(components) {
5554
const isSSR = this.el.hasAttribute("data-ssr");
5655

5756
if (isSSR) {
58-
this._root = ReactDOM.hydrateRoot(
59-
this.el,
60-
React.createElement(
61-
this._Component,
62-
getProps(this),
63-
...getChildren(this),
64-
),
57+
const tree = getComponentTree(
58+
this._Component,
59+
getProps(this),
60+
getChildren(this),
6561
);
62+
this._root = ReactDOM.hydrateRoot(this.el, tree);
6663
} else {
6764
this._root = ReactDOM.createRoot(this.el);
6865
this._render();

assets/js/live_react/index.mjs

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

assets/js/live_react/server.mjs

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import React from "react";
22
import { renderToString } from "react-dom/server";
3+
import { getComponentTree } from "./utils";
34

4-
function Wrapper({ children }) {
5-
return React.createElement(React.Fragment, null, children);
5+
function getChildren(slots) {
6+
if (!slots?.default) {
7+
return [];
8+
}
9+
10+
return [
11+
React.createElement("div", {
12+
dangerouslySetInnerHTML: { __html: slots.default.trim() },
13+
}),
14+
];
615
}
716

817
export function getRender(components) {
@@ -11,25 +20,10 @@ export function getRender(components) {
1120
if (!Component) {
1221
throw new Error(`Component "${name}" not found`);
1322
}
14-
15-
let children = [];
16-
if (slots?.default) {
17-
children.push(
18-
React.createElement("div", {
19-
dangerouslySetInnerHTML: { __html: slots.default.trim() },
20-
}),
21-
);
22-
}
23-
24-
// The Component need to be wrapped to prevent useState useEffect error which can't be root component
25-
const componentInstance = React.createElement(
26-
Component,
27-
props,
28-
...children,
29-
);
30-
const content = React.createElement(Wrapper, null, componentInstance);
23+
const children = getChildren(slots);
24+
const tree = getComponentTree(Component, props, children);
3125

3226
// https://react.dev/reference/react-dom/server/renderToString
33-
return renderToString(content);
27+
return renderToString(tree);
3428
};
3529
}

assets/js/live_react/utils.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from "react";
2+
import { LiveReactProvider } from "./context";
3+
4+
function getHooks(props) {
5+
return {
6+
pushEvent: props.pushEvent,
7+
pushEventTo: props.pushEventTo,
8+
handleEvent: props.handleEvent,
9+
removeHandleEvent: props.removeHandleEvent,
10+
upload: props.upload,
11+
uploadTo: props.uploadTo,
12+
};
13+
}
14+
15+
export function getComponentTree(Component, props, children) {
16+
const componentInstance = React.createElement(Component, props, ...children);
17+
18+
return React.createElement(
19+
LiveReactProvider,
20+
getHooks(props),
21+
componentInstance,
22+
);
23+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { useState } from "react";
2+
import { useLiveReact } from "live_react";
3+
4+
export function Context({ count }) {
5+
const [amount, setAmount] = useState(1);
6+
const { pushEvent, ...rest } = useLiveReact();
7+
console.log(rest);
8+
9+
return (
10+
<div className="flex flex-col justify-center items-center gap-4">
11+
<div className="flex flex-row items-center justify-center gap-10">
12+
<button
13+
className="px-4 py-2 rounded bg-red-500 text-white"
14+
onClick={() => pushEvent("set_count", { value: count - amount })}
15+
>
16+
-{amount}
17+
</button>
18+
<span className="text-xl">{count}</span>
19+
<button
20+
className="px-4 py-2 rounded bg-green-500 text-white"
21+
onClick={() => pushEvent("set_count", { value: count + amount })}
22+
>
23+
+{amount}
24+
</button>
25+
</div>
26+
<label>
27+
Amount:
28+
<input
29+
onChange={(e) => setAmount(parseInt(e.target.value, 10))}
30+
type="number"
31+
className="rounded"
32+
value={amount}
33+
min="1"
34+
/>
35+
</label>
36+
</div>
37+
);
38+
}

live_react_examples/assets/react-components/counter.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from "react";
22

3-
export function Counter({ count, onInc, onDec }) {
3+
export function Counter({ count }) {
44
const [amount, setAmount] = useState(1);
55

66
return (

live_react_examples/assets/react-components/index.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// polyfill recommended by Vite https://vitejs.dev/config/build-options#build-modulepreload
22
import "vite/modulepreload-polyfill";
33

4+
import { Context } from "./context";
45
import { Counter } from "./counter";
56
import { DelaySlider } from "./delay-slider";
67
import { FlashSonner } from "./flash-sonner";
@@ -14,6 +15,7 @@ import { Slot } from "./slot";
1415
import { Typescript } from "./typescript";
1516

1617
export default {
18+
Context,
1719
Counter,
1820
DelaySlider,
1921
FlashSonner,

live_react_examples/lib/live_react_examples.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ defmodule LiveReactExamples do
116116
}
117117
end
118118

119+
def demo(:context) do
120+
%{
121+
raw_view_url: "#{@raw_url}#{@live_views}/context.ex",
122+
view_url: "#{@url}#{@live_views}/context.ex",
123+
view_language: "elixir",
124+
raw_react_url: "#{@raw_url}#{@react}/context.jsx",
125+
react_url: "#{@url}#{@react}/context.jsx"
126+
}
127+
end
128+
119129
def demo(demo) do
120130
raise ArgumentError, "Unknown demo: #{inspect(demo)}"
121131
end

live_react_examples/lib/live_react_examples_web/components/layouts/app.html.heex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@
9595
>
9696
Slot
9797
</.link>
98+
<.link
99+
class="group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline font-medium text-zinc-700"
100+
navigate={~p"/context"}
101+
>
102+
Context
103+
</.link>
98104
</div>
99105
</div>
100106
</nav>

0 commit comments

Comments
 (0)