diff --git a/src/__tests__/useChannel.tsx b/src/__tests__/useChannel.tsx
index a9bb994..d8b89e3 100644
--- a/src/__tests__/useChannel.tsx
+++ b/src/__tests__/useChannel.tsx
@@ -2,33 +2,95 @@ import React from "react";
import { renderHook } from "@testing-library/react-hooks";
import { PusherProvider } from "../PusherProvider";
import { useChannel } from "../useChannel";
+import { cleanup } from "@testing-library/react";
+
+beforeEach(() => {
+ cleanup();
+ jest.resetAllMocks();
+});
jest.mock("pusher-js", () => {
- const { PusherMock } = require("../mocks.ts");
- return {
- __esModule: true,
- default: jest.fn(() => new PusherMock())
- };
+ const { PusherMock } = require("pusher-js-mock");
+ // monkey patch missing function
+ PusherMock.prototype.disconnect = () => {};
+ return PusherMock;
+});
+
+const config = {
+ clientKey: "client-key",
+ cluster: "ap4",
+ children: "Test"
+};
+
+test("should fill default options", () => {
+ const wrapper = ({ children }: any) => (
+ {children}
+ );
+ const { result, unmount } = renderHook(() => useChannel("my-channel"), {
+ wrapper
+ });
+
+ const { channel } = result.current;
+ expect(Object.keys(channel.callbacks)).toHaveLength(0);
+
+ unmount();
});
-test("should subscribe to channel and listen to events", async () => {
+test("should subscribe to channel and emit events", async () => {
const onEvent = jest.fn();
const wrapper = ({ children }: any) => (
-
- {children}
-
+ {children}
);
- const { result, unmount } = renderHook(
- () => useChannel("my-channel", "my-event", onEvent),
+ const { result, unmount, rerender } = renderHook(
+ () => useChannel("my-channel", "my-event", onEvent, [], { skip: false }),
{ wrapper }
);
- expect(result.current!.bind).toHaveBeenCalledTimes(1);
+ rerender();
+
+ const { channel } = result.current;
- result.current!.emit("my-event", "test");
+ channel.emit("my-event", "test");
+ expect(channel.callbacks["my-event"]).toBeTruthy();
expect(onEvent).toHaveBeenCalledTimes(1);
expect(onEvent).toHaveBeenCalledWith("test");
unmount();
- expect(result.current!.unbind).toHaveBeenCalledTimes(1);
+});
+
+test("should subscribe to channel as prop changes", () => {
+ const onEvent = jest.fn();
+ const wrapper = ({ children }: any) => (
+ {children}
+ );
+ const { result, unmount, rerender } = renderHook(
+ ([a = "my-channel", b = "my-event", c = onEvent]: any) =>
+ useChannel(a, b, c),
+ { wrapper }
+ );
+
+ rerender(["your-channel", "some-event", onEvent]);
+ const { channel } = result.current;
+
+ // simulate event
+ channel.emit("some-event", "test");
+
+ expect(onEvent).toHaveBeenCalledTimes(1);
+ expect(onEvent).toHaveBeenCalledWith("test");
+ unmount();
+});
+
+test("should skip channel subscription if option is passed", () => {
+ const wrapper = ({ children }: any) => (
+ {children}
+ );
+ const { result, unmount } = renderHook(
+ () => useChannel("a", "b", () => {}, [], { skip: true }),
+ { wrapper }
+ );
+
+ const { channel } = result.current;
+ expect(channel).toBeUndefined();
+
+ unmount();
});
diff --git a/src/useChannel.ts b/src/useChannel.ts
index d566e17..6b4a1ce 100644
--- a/src/useChannel.ts
+++ b/src/useChannel.ts
@@ -1,6 +1,6 @@
-import { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
-import { Channel, EventCallback } from "pusher-js";
+import { EventCallback } from "pusher-js";
import invariant from "invariant";
import { usePusher } from "./usePusher";
@@ -26,8 +26,6 @@ export function useChannel(
) {
// errors for missing arguments
invariant(channelName, "channelName required to subscribe to a channel");
- invariant(eventName, "eventName required to bind to an event");
- invariant(onEvent, "onEvent required to callback on event");
// initialise defaults
const defaultOptions = { skip: false };
@@ -39,35 +37,50 @@ export function useChannel(
// hook setup
const { client } = usePusher();
- const callback = useCallback(eventHandler, deps);
- const [channel, setChannel] = useState();
+ const [channel, setChannel] = useState();
+ /**
+ * Channel subscription
+ */
useEffect(() => {
- if (client && !hookOptions.skip && channelName) {
+ if (client.current && !hookOptions.skip) {
// subscribe to the channel
- const pusherChannel = client.subscribe(channelName);
- // if there's an eventName, bind to it.
- eventName && pusherChannel.bind(eventName, callback);
-
- // store the ref for cleanup
+ const pusherChannel = client.current.subscribe(channelName);
setChannel(pusherChannel);
}
- }, [client, callback, hookOptions.skip, eventName, channelName]);
+ }, [client.current, hookOptions.skip]);
- // cleanup on unmount
- useEffect(
- () => () => {
- if (client && channel) {
- client.unsubscribe(channelName);
- }
+ const previousChannelName = useRef();
+ useEffect(() => {
+ if (previousChannelName.current === channelName) return;
+ const clientRef = client.current;
+ return () => {
+ clientRef.unsubscribe(channelName);
+ };
+ });
+ // track channelName for unsubscription.
+ useEffect(() => {
+ previousChannelName.current = channelName;
+ });
+ /**
+ * Event binding
+ */
+ const callback = useCallback(eventHandler, deps);
+ useEffect(() => {
+ if (channel && eventName && !hookOptions.skip) {
+ channel.bind(eventName, callback);
+ }
+ }, [channel, eventName, hookOptions.skip, callback]);
+
+ // when the callback changes, unbind the old one.
+ useEffect(() => {
+ return () => {
if (client && channel && eventName) {
channel.unbind(eventName, callback);
}
- },
- [client, channel]
- );
+ };
+ }, [client, channel, callback, eventName]);
- // return channel instance back for unsafe things like channel.trigger()
- return channel;
+ return { channel };
}