@laravel/stream-react
Version:
Laravel streaming hooks for React
303 lines (231 loc) • 8.47 kB
Markdown
# Laravel Stream for React
<p align="left">
<a href="https://github.com/laravel/stream/actions/workflows/tests.yml"><img src="https://github.com/laravel/stream/actions/workflows/tests.yml/badge.svg" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/@laravel/stream-react"><img src="https://img.shields.io/npm/dt/@laravel/stream-react" alt="Total Downloads"></a>
<a href="https://www.npmjs.com/package/@laravel/stream-react"><img src="https://img.shields.io/npm/v/@laravel/stream-react" alt="Latest Stable Version"></a>
<a href="https://www.npmjs.com/package/@laravel/stream-react"><img src="https://img.shields.io/npm/l/@laravel/stream-react" alt="License"></a>
</p>
Easily consume streams in your React application.
## Installation
```bash
npm install /stream-react
```
## Streaming Responses
> [!IMPORTANT]
> The `useStream` hook is currently in Beta, the API is subject to change prior to the v1.0.0 release. All notable changes will be documented in the [changelog](./../../CHANGELOG.md).
The `useStream` hook allows you to seamlessly consume [streamed responses](https://laravel.com/docs/responses#streamed-responses) in your React application.
Provide your stream URL and the hook will automatically update `data` with the concatenated response as data is returned from your server:
```tsx
import { useStream } from "@laravel/stream-react";
function App() {
const { data, isFetching, isStreaming, send } = useStream("chat");
const sendMessage = () => {
send({
message: `Current timestamp: ${Date.now()}`,
});
};
return (
<div>
<div>{data}</div>
{isFetching && <div>Connecting...</div>}
{isStreaming && <div>Generating...</div>}
<button onClick={sendMessage}>Send Message</button>
</div>
);
}
```
When sending data back to the stream, the active connection to the stream is canceled before sending the new data. All requests are sent as JSON `POST` requests.
The second argument given to `useStream` is an options object that you may use to customize the stream consumption behavior:
```ts
type StreamOptions = {
id?: string;
initialInput?: Record<string, any>;
headers?: Record<string, string>;
csrfToken?: string;
json?: boolean;
credentials?: RequestCredentials;
onResponse?: (response: Response) => void;
onData?: (data: string) => void;
onCancel?: () => void;
onFinish?: () => void;
onError?: (error: Error) => void;
onBeforeSend?: (request: RequestInit) => boolean | RequestInit | void;
};
```
`onResponse` is triggered after a successful initial response from the stream and the raw [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) is passed to the callback.
`onData` is called as each chunk is received, the current chunk is passed to the callback.
`onFinish` is called when a stream has finished and when an error is thrown during the fetch/read cycle.
`onBeforeSend` is called right before sending the request to the server and receives the `RequestInit` object as an argument. Returning `false` from this callback cancels the request, returning a [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object will override the existing `RequestInit` object.
By default, a request is not made the to stream on initialization. You may pass an initial payload to the stream by using the `initialInput` option:
```tsx
import { useStream } from "@laravel/stream-react";
function App() {
const { data } = useStream("chat", {
initialInput: {
message: "Introduce yourself.",
},
});
return <div>{data}</div>;
}
```
To cancel a stream manually, you may use the `cancel` method returned from the hook:
```tsx
import { useStream } from "@laravel/stream-react";
function App() {
const { data, cancel } = useStream("chat");
return (
<div>
<div>{data}</div>
<button onClick={cancel}>Cancel</button>
</div>
);
}
```
Each time the `useStream` hook is used, a random `id` is generated to identify the stream. This is sent back to the server with each request in the `X-STREAM-ID` header.
When consuming the same stream from multiple components, you can read and write to the stream by providing your own `id`:
```tsx
// App.tsx
import { useStream } from "@laravel/stream-react";
function App() {
const { data, id } = useStream("chat");
return (
<div>
<div>{data}</div>
<StreamStatus id={id} />
</div>
);
}
// StreamStatus.tsx
import { useStream } from "@laravel/stream-react";
function StreamStatus({ id }) {
const { isFetching, isStreaming } = useStream("chat", { id });
return (
<div>
{isFetching && <div>Connecting...</div>}
{isStreaming && <div>Generating...</div>}
</div>
);
}
```
The `useJsonStream` hook is identical to the `useStream` hook except that it will attempt to parse the data as JSON once it has finished streaming:
```tsx
import { useJsonStream } from "@laravel/stream-react";
type User = {
id: number;
name: string;
email: string;
};
function App() {
const { data, send } = useJsonStream<{ users: User[] }>("users");
const loadUsers = () => {
send({
query: "taylor",
});
};
return (
<div>
<ul>
{data?.users.map((user) => (
<li>
{user.id}: {user.name}
</li>
))}
</ul>
<button onClick={loadUsers}>Load Users</button>
</div>
);
}
```
## Event Streams (SSE)
The `useEventStream` hook allows you to seamlessly consume [Server-Sent Events (SSE)](https://laravel.com/docs/responses#event-streams) in your React application.
Provide your stream URL and the hook will automatically update `message` with the concatenated response as messages are returned from your server:
```tsx
import { useEventStream } from "@laravel/stream-react";
function App() {
const { message } = useEventStream("/stream");
return <div>{message}</div>;
}
```
You also have access to the array of message parts:
```tsx
import { useEventStream } from "@laravel/stream-react";
function App() {
const { messageParts } = useEventStream("/stream");
return (
<ul>
{messageParts.forEach((message) => (
<li>{message}</li>
))}
</ul>
);
}
```
If you'd like to listen to multiple events:
```tsx
import { useEventStream } from "@laravel/stream-react";
function App() {
useEventStream("/stream", {
eventName: ["update", "create"],
onMessage: (event) => {
if (event.type === "update") {
// Handle update
} else {
// Handle create
}
},
});
return null;
}
```
The second parameter is an options object where all properties are optional (defaults are shown below):
```tsx
import { useEventStream } from "@laravel/stream-react";
function App() {
const { message } = useEventStream("/stream", {
event: "update",
onMessage: (message) => {
//
},
onError: (error) => {
//
},
onComplete: () => {
//
},
endSignal: "</stream>",
glue: " ",
replace: false,
});
return <div>{message}</div>;
}
```
You can close the connection manually by using the returned `close` function:
```tsx
import { useEventStream } from "@laravel/stream-react";
import { useEffect } from "react";
function App() {
const { message, close } = useEventStream("/stream");
useEffect(() => {
setTimeout(() => {
close();
}, 3000);
}, []);
return <div>{message}</div>;
}
```
The `clearMessage` function may be used to clear the message content that has been received so far:
```tsx
import { useEventStream } from "@laravel/stream-react";
import { useEffect } from "react";
function App() {
const { message, clearMessage } = useEventStream("/stream");
useEffect(() => {
setTimeout(() => {
clearMessage();
}, 3000);
}, []);
return <div>{message}</div>;
}
```
## License
Laravel Stream is open-sourced software licensed under the [MIT license](LICENSE.md).