use-pusher

A wrapper around pusher-js for easy-as hooks in React.

Usage no npm install needed!

<script type="module">
  import usePusher from 'https://cdn.skypack.dev/use-pusher';
</script>

README

use-pusher

Easy as hooks that integrate with the pusher-js library.

NPM Typed

⚠️ this package has moved to @harelpls/use-pusher - please update your dependencies!


API Reference/Docs

Install

yarn add @harelpls/use-pusher

Hooks

Usage

You must wrap your app with a PusherProvider and pass it config props for pusher-js initialisation.

import React from 'react';
import { PusherProvider } from '@harelpls/use-pusher';

const config = {
  // required config props
  clientKey: 'client-key',
  cluster: 'ap4',

  // optional if you'd like to trigger events. BYO endpoint.
  // see "Trigger Server" below for more info
  triggerEndpoint: '/pusher/trigger',

  // required for private/presence channels
  // also sends auth headers to trigger endpoint
  authEndpoint: '/pusher/auth',
  auth: {
    headers: { Authorization: 'Bearer token' },
  },
};

// Wrap app in provider
const App = () => {
  <PusherProvider {...config}>
    <Example />
  </PusherProvider>;
};

useChannel

Use this hook to subscribe to a channel.

// returns channel instance.
const channel = useChannel('channel-name');

usePresenceChannel

Augments a regular channel with member functionality.

const Example = () => {
  const { members, myID } = usePresenceChannel('presence-awesome');

  return (
    <ul>
      {Object.entries(members)
        // filter self from members
        .filter([id] => id !== myID)
        // map them to a list
        .map([id, info] => (
          <li key={id}>{info.name}</li>
        ))
      }
    </ul>
  )
}

useEvent

Bind to events on a channel with a callback.

const Example = () => {
  const [message, setMessages] = useState();
  const channel = useChannel('channel-name');
  useEvent(
    channel,
    'message',
    ({ data }) => setMessages(messages => [...messages, data]),
    // optional dependencies array. Passed through to useCallback. Defaults to [].
    []
  );
};

useTrigger

A helper function to create a server triggered event. BYO server (See Trigger Server below). Pass in triggerEndpoint prop to <PusherProvider />. Any auth headers from config.auth.headers automatically get passed to the fetch call.

import { useTrigger } from '@harelpls/use-pusher';

const Example = () => {.
  const trigger = useTrigger();
  const handleClick = () =>
    trigger("channel-name", "event-name", "hello");

  return (
    <button onClick={handleClick}>Say Hello</button>
  )
}

usePusher

Get access to the Pusher instance to do other things.

import { usePusher } from '@harelpls/use-pusher';

const Example = () => {
  const { client } = usePusher();
  client.log('Look ma, logs!');

  return null;
};

Trigger Server

In order to trigger an event, you'll have to create a simple lambda (or an express server if that's your thing). Below is a short lambda that can handle triggered events from useTrigger.

import Pusher from 'pusher';

const pusher = new Pusher({
  appId: 'app-id',
  key: 'client-key',
  secret: 'mad-secret',
  cluster: 'ap4',
});

export async function handler(event) {
  const { channelName, eventName, data } = JSON.parse(event.body);
  pusher.trigger(channelName, eventName, data);
  return { statusCode: 200 };
}

useClientTrigger

I don't want a server though

I hear ya. If you're feeling audacious, you can use client events to push directly from the client:

import { useChannel, useClientTrigger } from '@harelpls/use-pusher';

const Example = () => {
  const channel = useChannel('presence-danger-zone');
  const trigger = useClientTrigger(channel);
  const handleClientEvent = () => {
    trigger('Pew pew');
  };

  return <button onClick={handleClientEvent}>Fire</button>;
};

Typescript

This project was built using typescript, so types are built-in. Yeeeew!

Testing

Typed PusherMock, PusherChannelMock and PusherPresenceChannelMock utils are provided based on pusher-js-mock (thanks mate 🙏). Use these to stub out the client and channels, with an additional emit method on the channel classes.

Testing emitted events with jest can be achieved using jest.mock and @testing-library/react (or enzyme, though your tests should reflect what the user should see NOT how the component handles events internally):

// Example.tsx
import React from 'react';
import { useChannel, useEvent } from '@harelpls/use-pusher';

const Example = () => {
  const [title, setTitle] = useState();
  const channel = useChannel('my-channel');
  useEvent(channel, 'title', ({ data }) => setTitle(data));

  return <span>{title}</span>;
};

// Example.test.tsx
import { render, act } from '@testing-library/react';
import { PusherMock, PusherChannelMock } from '@harelpls/use-pusher';

// mock out the result of the useChannel hook
const mockChannel = new PusherChannelMock();
jest.mock('@harelpls/use-pusher', () => ({
  ...require.requireActual('@harelpls/use-pusher'),
  useChannel: () => mockChannel,
}));

test('should show a title when it receives a title event', async () => {
  // mock the client
  const client = { current: new PusherMock('client-key', { cluster: 'ap4' }) };

  // render component and provider with a mocked context value
  const { findByText } = render(
    <PusherProvider clientKey="client-key" cluster="ap4" value={{ client }}>
      <Example />
    </PusherProvider>
  );

  // emit an event on the mocked channel
  act(() => mockChannel.emit('title', { data: 'Hello world' }));

  // assert expectations
  expect(await findByText('Hello world')).toBeInTheDocument();
});

Check out the example tests for testing presence channels.

Contributing

  1. Clone the repository and run yarn && yarn test:watch
  2. Get coding!

Please write tests (100% jest coverage) and types.

License

MIT © @mayteio


This hook is created using create-react-hook.