Skip to content

Instantly share code, notes, and snippets.

@dennyabrain
Created February 23, 2025 06:35
Add React Components to a Phoenix App without adding additional dependencies
import { CounterHook } from "./counter";
let Hooks = {
CounterHook,
};
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken },
hooks: Hooks,
});
config :esbuild,
version: "0.17.11",
contract: [
args:
~w(js/app.js --loader:.js=jsx --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
import React, { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
const CounterComponent = ({ id, send, receive, start }) => {
const [count, setCount] = useState(start);
const [serverMessage, setServerMessage] = useState("");
useEffect(() => {
receive(`${id}:even`, (event) => {
setServerMessage(event.msg);
});
receive(`${id}:odd`, (event) => {
setServerMessage("");
});
}, []);
return (
<div className="flex flex-row gap-8 items-center ">
<button
className="border-2 rounded-md px-4 py-2 bg-lime-300"
onClick={() => setCount(count + 1)}
>
+
</button>
<p className="text-lg">{count}</p>
<button
className="border-2 rounded-md px-4 py-2 bg-lime-300"
onClick={() => setCount(count + 1)}
>
-
</button>
<button
onClick={() =>
send("from-react", { id, count }, (reply) => {
console.log(reply);
})
}
>
Send To server
</button>
<p className="text-red-600">{serverMessage}</p>
</div>
);
};
export var CounterHook = {
mounted() {
let el = this.el;
let id = el.getAttribute("id");
let start = parseInt(el.getAttribute("start"));
const root = createRoot(el);
root.render(
<CounterComponent
id={id}
start={start}
send={this.pushEvent.bind(this)}
receive={this.handleEvent.bind(this)}
/>
);
},
};
defmodule ContractWeb.DesignLive.Create do
use ContractWeb, :live_view
use ContractWeb, :html
def mount(_params, session, socket) do
socket = socket |> assign(:count, 5)
{:ok, socket}
end
def handle_event("from-react", params, socket) do
IO.inspect(params)
count = params["count"]
id = params["id"]
socket =
case rem(count, 2) do
0 -> push_event(socket, "#{id}:even", %{msg: "even"})
1 -> push_event(socket, "#{id}:odd", %{msg: "odd"})
end
{:noreply, socket}
# {:reply, %{msg: "ok"}, socket}
# {:noreply, push_event(socket, "ack", %{msg: "ok"})}
end
end
<section>
<div id="counter-a" phx-hook="CounterHook" start={@count} />
<div id="counter-b" phx-hook="CounterHook" start={9} />
</section>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment