Coder Social home page Coder Social logo

ably-labs / react-hooks Goto Github PK

View Code? Open in Web Editor NEW
38.0 7.0 9.0 459 KB

The recommended way to use Ably in React components

Home Page: https://www.npmjs.com/package/@ably-labs/react-hooks

License: MIT License

HTML 1.33% CSS 0.24% TypeScript 98.43%
react react-hooks npm-package realtime-messaging

react-hooks's Introduction

@ably-labs/react-hooks

Important

The release candidate of our 3.0 react hooks library is currently available as a module of ably-js, which includes major improvements such as using a context provider and providing APIs to react to connection errors and channel errors. Please see the new react hooks README for installation instructions and usage examples.

Use Ably in your React application using idiomatic, easy to use, React Hooks!

Using this package you can:

  • Interact with Ably channels using a react hook.
  • Send messages via Ably using the channel instances the hooks provide
  • Get notifications of user presence on channels
  • Send presence updates

The hooks provide a simplified syntax for interacting with Ably, and manage the lifecycle of the Ably SDK instances for you taking care to subscribe and unsubscribe to channels and events when your react components re-render.



Installation

The hooks ship as an ES6 module, so you can use the import syntax in your react code.

npm install --save @ably-labs/react-hooks

This works out of the box using create-react-app - and you can use the package immediately.

Compatible React Versions

The latest version of this package tracks the latest version of react.

React Version @ably-labs/react-hooks Version
>=17.0.2 1.1.8
>=18.1.0 2.0.x (current)

Ably channels and API keys

In order to use these hooks, you will need an Ably API key. If you are not already signed up, you can sign up now for a free Ably account. Once you have an Ably account:

  1. Log into your app dashboard.
  2. Under “Your apps”, click on “Manage app” for any app you wish to use for this tutorial, or create a new one with the “Create New App” button.
  3. Click on the “API Keys” tab.
  4. Copy the secret “API Key” value from your Root key, we will use this later when we build our app.

It is strongly recommended that you use Token Authentication, this will require server side code that is outside of the scope of this readme. In the examples below we use an API key directly in the markup, this is for *local development only and should not be used for production code and should not be committed to your repositories.


Usage

Once you've added the package using npm to your project, you can use the hooks in your react code.

Start by adding a reference to the hooks

import { configureAbly, useChannel } from "@ably-labs/react-hooks";

Then you need to use the configureAbly function to create an instance of the Ably JavaScript SDK.

configureAbly({ key: "your-ably-api-key", clientId: generateRandomId() });

configureAbly matches the method signature of the Ably SDK - and requires either a string or an AblyClientOptions. You can use this configuration object to setup your API keys, or tokenAuthentication as you normally would. If you want to use the usePresence hook, you'll need to explicitly provide a clientId.

Once you've done this, you can use the hooks in your code. The simplest example is as follows:

const [channel] = useChannel("your-channel-name", (message) => {
    console.log(message);
});

Every time a message is sent to your-channel-name it'll be logged to the console. You can do whatever you need to with those messages.


useChannel

The useChannel hook lets you subscribe to a channel and receive messages from it.

const [channel, ably] = useChannel("your-channel-name", (message) => {
    console.log(message);
});

Both the channel instance, and the Ably JavaScript SDK instance are returned from the useChannel call.

useChannel really shines when combined with a regular react useState hook - for example, you could keep a list of messages in your app state, and use the useChannel hook to subscribe to a channel, and update the state when new messages arrive.

const [messages, updateMessages] = useState([]);
const [channel] = useChannel("your-channel-name", (message) => {
    updateMessages((prev) => [...prev, message]);
});

// Convert the messages to list items to render in a react component
const messagePreviews = messages.map((msg, index) => <li key={index}>{msg.data.someProperty}</li>);

useChannel supports all of the parameter combinations of a regular call to channel.subscribe, so you can filter the messages you subscribe to by providing a message type to the useChannel function:

const [channel] = useChannel("your-channel-name", "test-message", (message) => {
    console.log(message); // Only logs messages sent using the `test-message` message type
});

The channel instance returned by useChannel can be used to send messages to the channel. It's just a regular Ably JavaScript SDK channel instance.

channel.publish("test-message", { text: "message text" });

Because we're returning the channel instance, and Ably SDK instance from our useChannel hook, you can subsequently use these to perform any operations you like on the channel.

For example, you could retrieve history like this:

const [channel] = useChannel("your-channel-name", (message) => {
    console.log(message);
});

const history = channel.history((err, result) => {
    var lastMessage = resultPage.items[0];
    console.log('Last message: ' + lastMessage.id + ' - ' + lastMessage.data);
});

It's also worth highlighting that the useChannel hook supports all of the additional parameters that the regular Ably SDK does as we're simply passing the call along. This means you can use features like rewind:

const [channel] = useChannel("[?rewind=100]your-channel-name", (message) => {
    // This call will rewind 100 messages
    console.log(message);
});

We support providing ChannelOptions to the useChannel hook:

const [channel] = useChannel({ channelName: "your-channel-name", options: { ... } }, (message) => {
    ...
});

We also support providing your own Realtime instance to useChannel, which may be useful if you need to have more than one Ably client on the same page:

import { useChannel, Realtime } from '@ably-labs/react-hooks'

const realtime = new Realtime(options);

useChannel({ channelName: "your-channel-name", realtime: realtime }, (message) => {
    ...
})

for any cases where channel options must be provided (e.g. setting up encryption cypher keys).


usePresence

The usePresence hook lets you subscribe to presence events on a channel - this will allow you to get notified when a user joins or leaves the channel.

Please note that fetching present members is executed as an effect, so it'll load in after your component renders for the first time.

const [presenceData, updateStatus] = usePresence("your-channel-name");

// Convert presence data to list items to render    
const peers = presenceData.map((msg, index) => <li key={index}>{msg.clientId}: {msg.data}</li>);

usePresence returns an array of presence messages - again each message is a regular Ably JavaScript SDK presenceMessage instance.

You can optionally provide a string when you usePresence to set an initial presence data string.

const [presenceData, updateStatus] = usePresence("your-channel-name", "initial state");

// The `updateStatus` function can be used to update the presence data for the current client
updateStatus("new status");

The new state will be sent to the channel, and any other clients subscribed to the channel will be notified of the change immediately.

If you don't want to use the presenceData returned from usePresence, you can configure a callback

const [_, updateStatus] = usePresence("your-channel-name", "initial state", (presenceUpdate) => {
    console.log(presenceUpdate);
});

usePresence supports objects, as well as strings

usePresence("your-channel-name", { foo: "bar" });

and if you're using TypeScript there are type hints to make sure that updates are of the same type as your initial constraint, or a provided generic type parameter:

const TypedUsePresenceComponent = () => {
    // In this example MyPresenceType will be checked - if omitted, the shape of the initial 
    // value will be used ...and if that's omitted, `any` will be the default.

    const [val] = usePresence<MyPresenceType>("testChannelName", { foo: "bar" });

    return (
        <div role='presence'>
            {JSON.stringify(val)}
        </div>
    );
}

interface MyPresenceType {
    foo: string;
}

PresenceData is a good way to store synchronised, per-client metadata, so types here are especially valuable.

We also support providing your own Realtime instance to usePresence, which may be useful if you need to have more than one Ably client on the same page:

import { useChannel, Realtime } from '@ably-labs/react-hooks'

const realtime = new Realtime(options);

usePresence({ channelName: "your-channel-name", realtime: realtime }, (presenceUpdate) => {
    ...
})

react-hooks's People

Contributors

davidwhitney avatar dependabot[bot] avatar mohyour avatar owenpearson avatar peter-maguire avatar srushtika avatar stmoreau avatar tbedford avatar thisisjofrank avatar tomczoink avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-hooks's Issues

Issue with HMR (Fast Refresh) in NextJS

Hi,
I just tried the hooks on a very simple project with NextJS. In development, I get an error on HMR (Fast Refresh), because (I think) the connection is closed and open too fast.

image

Did you already had this issue? Do you know a workaround?

If not, I will be able to provide you a code sandbox 😊

Add hook for listening to connection state

These should allow users to listen to connection state along with state errors in a way that is idiomatic for react applications. Ideally these should allow users to control whether a component re-renders upon a given state change.

SyntaxError: Unexpected token 'export' with Next.js

Context

ably-labs/react-hooks exports as a ES6 module. Unfortunately, Next.js doesn't transpile functions imported from node_modules. As a result, importing ably-labs/react-hooks causes an issue (SyntaxError: Unexpected token 'export' with Next.js)

Possible Fix

Update the documentation and explain that for Next.js, developers need to install next-transpile-modules and update next.config.js with:

const withTM = require('next-transpile-modules')(['@ably-labs/react-hooks']);

module.exports = withTM({...})

FEATURE: A way to force refresh the token, or clear the existing instance / client

Hello, So far I have not found a way to force refresh an authentication token to update user claims or clear the instance. The use cases are the following:

The user token is scoped to "workspaces" if the user creates a new workspace or is a new user, the claims need to be updated. There does not currently appear to be a way to do this.

If the user logs out, the token / client ID should be able to be cleared so that if a new user logs in they don't get the same ably instance as the previous user. Currently, a hard refresh of the page is required.

Please let me know if there is a better or existing way to achieve this.

Channel operation failed as channel state is detaching

Hi,

I'm seeing this uncaught exception when trying to use the usePresence hook:

Screenshot 2022-08-11 at 00 20 11

This happens when my component unmounts and the hook tries to leave the channel.

I think my setup might contribute to the problem. Basically I have a component like this:

function MyComponent({ roomId }) {
    usePresence(roomId)
}

// Usage
<MyComponent roomId={roomId} key={`${roomId}`}

When the roomId changes, the component gets re-rendered, which I think causes the channel to start detaching. At the same time, it unmounts, which calls channel.presence.leave, hence the exception.

usePresence throws error if Ably is not configured

If usePresence or useChannel is used without an internet connection (and Ably has not been activated yet), I get this error:

```Error: Ably not configured - please call configureAbly({ key: "your-api-key", clientId: "someid" });``

While I'm sure this might technically be true, it is coming from assertConfiguration. My expectation is this will fail silently in this scenario.

In general, it is bad form to throw an error inside a hook because it can result in an inconsistent state in the app. Instead it would be preferable to return functions which throw errors - that way the errors can be caught appropriately and dealt with.

Reconsider error handling strategy

Related to #47 - we should ensure that errors aren't thrown inside hooks - these should either be returned from the hook or thrown in a way which can be handled from an error boundary.

Also, as part of this work we should consider the behaviour of useChannel and usePresence when a connection/channel error state is reached.

Improve test coverage

Currently our tests in this repo are quite rudimentary, a few things we could consider to improve this:

  • adding unit tests
  • migrating from vitest to a more mature setup using jest + @testing-library/react
  • adding tests with a live connection using the sandbox env

Get the current message in `usePresence`

I'm using usePresence like this:

const [presenceData, updateStatus] = usePresence('connect', 'A person name')

I want to have an input to change the message. Something like this:

<input
  value={status}
  onChange={() => {
    // update status
  }}
/>

I could use a useState for status, but I think that would result in duplicate data and could lead to inconsistencies.

Use `ably-js` as peer dependency

Using a library as a peerDependency offers several advantages, including: reduced bundle size, simpler updates, clear separation between ably react-hooks and ably-js

Add hook for listening to channel state

These should allow users to listen to channel state along with state errors in a way that is idiomatic for react applications. Ideally these should allow users to control whether a component re-renders upon a given state change.

Auth.requestToken(): `input` must not start with a slash when using `prefixUrl`

Context

I have installed ably-labs/react-hooks in order to use the configureAbly() and useChannel hook in My Next.js app. I created an API endpoint for authentication:

import Ably from "ably/promises";

export default async function handler(req, res) {
    const client = new Ably.Realtime(process.env.ABLY_API_KEY);
    const tokenRequestData = await client.auth.createTokenRequest({ clientId: 'ably-blog-app' });
    res.status(200).json(tokenRequestData);
};

And called configureAbly in my component:

configureAbly({ authUrl: '/api/createTokenRequest' });

It works fine apart from the fact that I get log messages in my server:

Ably: Auth.requestToken(): token request signing call returned error; err = Error: `input` must not start with a slash when using `prefixUrl`

I have tried removing the slash (this causes an issue) and I have moved the configureAbly in various places (inside a component, in _app.js), but it still appears. There is a new one every 30 seconds. Any idea what might cause it?

useChannel Borked

useChannel doesnt have access to any state.

The data gets refreshed from the server.

When I try to check the data value from the server its undefined. So If i wanted to update state from the ably message I cannot.

const [data, setData] = useState();

configureAbly({ key: ablyClientKey, clientId: "1232" });

const [channel, ably] = useChannel(`channel`, (message) => {
    console.log(data) // This is triggered minutes later from the ruby code as data is updated and is always the initialized value not the refreshed value.
  });

  useEffect(() => {
    refreshData()
  }, []);

  function refreshData() {
    axios.get(`/get_data`, {  })
      .then(res => {
        setData(res)
      }).catch((error) => {
      // Error
        console.log(error.response);
      })
  }

`usePresence`: `updateStatus` changes every render

With usePresence, I was trying to call updateStatus when user information changes, but dropping it in a useEffect hook was triggering it in a loop since the updateStatus function changes every render.

Can that function be wrapped in useCallback() or something to prevent it from triggering updates?

Example of the problem:

const [userData] = useUserData();
const [users, updateStatus] = usePresence('channel-name');

useEffect(()=>{
  if ( userData ) updateStatus(userData);
},[userData, updateStatus]); // Infinite loop!

The "solution" right now is to just leave updateStatus off the dependency array, but that's not ideal since linters and React complain about "exhaustive dependencies".

useEffect(()=>{
  if ( userData ) updateStatus(userData);
},[userData]); // No infinite loop

How to catch failed rest error

Heya, so we're getting this log (I think due to brave browser blocking requests) - it does eventually connect (as the error code associated with this log says it will keep retrying), but ideally we'd catch the log and squash it.

Where do I catch this error?

`usePresence` hook fails to get initial snapshot when capability is only subscribe

I am using the usePresence hook in my application. My capability is set to only subscribe, so I am unable to call channel.presence.enter(). In the current implementation of the onMount function, an error is thrown when calling await channel.presence.enter(messageOrPresenceObject);, causing the initial presence snapshot not to be fetched.

const onMount = async () => {
    channel.presence.subscribe('enter', updatePresence);
    channel.presence.subscribe('leave', updatePresence);
    channel.presence.subscribe('update', updatePresence);

    await channel.presence.enter(messageOrPresenceObject); // add try-catch block here to handle error

    const snapshot = await channel.presence.get();
    updatePresenceData(snapshot);
}

2.0.10 throws typescript errror

Type error: Could not find a declaration file for module '@ably-labs/react-hooks'. '/projects/lmb-saas/web/node_modules/@ably-labs/react-hooks/dist/cjs/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/ably-labs__react-hooks` if it exists or add a new declaration (.d.ts) file containing `declare module '@ably-labs/react-hooks';`

> 5 | import { configureAbly } from "@ably-labs/react-hooks";

it didn't happen with 2.0.9

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.