Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jdyn committed Aug 12, 2023
0 parents commit f2d003c
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Empty file added index.ts
Empty file.
93 changes: 93 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "use-phoenix",
"version": "0.0.1",
"description": "React hooks for the Phoenix Framework",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jaden Moore",
"license": "MIT",
"dependencies": {
"phoenix": "^1.7.7"
},
"peerDependencies": {
"react": "^18.0.0"
},
"devDependencies": {
"@types/phoenix": "^1.6.0",
"@types/react": "^18.2.20"
}
}
58 changes: 58 additions & 0 deletions src/PhoenixProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useEffect } from "react";
import { Socket, SocketConnectOption } from "phoenix";

const PhoenixContext = React.createContext<Socket | null>(null);

export const useSocket = () => React.useContext(PhoenixContext);

export type SocketProviderProps = {
children?: React.ReactNode;
options?: SocketConnectOption;
url: string;
connect?: boolean;
onOpen?: () => void;
onClose?: () => void;
onError?: () => void;
};

export function PhoenixProvider(props: SocketProviderProps) {
const { children, connect, url, options } = props;
const socketRef = React.useRef<Socket | null>(null);

function closeSocket() {
socketRef.current?.disconnect();
socketRef.current = null;
}

useEffect(() => {
const { onOpen, onClose, onError } = props;
if (socketRef.current) {
closeSocket();
}

if (!connect) return;

const socket = new Socket(url, options);
socket.connect();
socketRef.current = socket;

if (onOpen) socket.onOpen(onOpen);
if (onClose) socket.onClose(onClose);
if (onError) socket.onError(onError);

return () => {
closeSocket();
};
}, [connect, url, options]);

return <PhoenixContext.Provider value={socketRef.current}>{children}</PhoenixContext.Provider>;
};

PhoenixProvider.defaultProps = {
options: {},
onOpen: null,
onClose: null,
onError: null,
connect: true,
children: null,
};
47 changes: 47 additions & 0 deletions src/useChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Channel, Push } from "phoenix";
import { useSocket } from "./PhoenixProvider";
import { useEffect, useRef } from "react";

type PushFunction = <PushResponse, PushAction extends { type: string; payload: Record<string, any> }>(
event: PushAction["type"],
payload: PushAction["payload"]
) => Promise<PushResponse>;

export function useChannel<Params extends Record<string, unknown> = {}, JoinPayload = null>(
topic: string,
options?: { params?: Params; onJoin?: (payload: JoinPayload) => void }
): [Channel | null, PushFunction] {
const { params, onJoin } = options || {};
const socket = useSocket();
const channelRef = useRef<Channel | null>(null);
const onJoinRef = useRef(onJoin);

onJoinRef.current = onJoin;

useEffect(() => {
if (socket === null) return;

const channel = socket.channel(topic, params);
channel.join().receive("ok", (payload) => onJoinRef.current && onJoinRef.current(payload));
channelRef.current = channel;

return () => {
channel.leave();
channelRef.current = null;
};
}, [socket, topic, params]);

const push: PushFunction = (event, payload) => {
return pushPromise(channelRef.current?.push(event, payload ?? {}));
};

return [channelRef.current, push];
}

const pushPromise = <Response>(push: Push | undefined): Promise<Response> =>
new Promise((resolve, reject) => {
if (!push) return reject("no push");

push.receive("ok", resolve).receive("error", reject);
// .receive('timeout', reject('timeout'));
});

0 comments on commit f2d003c

Please sign in to comment.