Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@
},
"devDependencies": {
"prettier": "^3.5.3"
},
"dependencies": {
"nanoid": "^5.1.5"
}
}
137 changes: 134 additions & 3 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,148 @@
<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 [Server-Sent Events (SSE)](https://laravel.com/docs/responses#event-streams) in your React application.
Easily consume streams in your React application.

## Installation

```bash
npm install @laravel/stream-react
```

## Usage
## Streaming Responses

Provide your stream URL and the hook will automatically update the `message` with the concatenated response as messages are returned from your server:
> [!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. The default values for this object are shown below:

```tsx
import { useStream } from "@laravel/stream-react";

function App() {
const { data } = useStream("chat", {
id: undefined,
initialInput: undefined,
headers: undefined,
csrfToken: undefined,
onResponse: (response: Response) => void,
onData: (data: string) => void,
onCancel: () => void,
onFinish: () => void,
onError: (error: Error) => void,
});

return <div>{data}</div>;
}
```

`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.

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>
);
}
```

## 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";
Expand Down
3 changes: 2 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@
"@vitejs/plugin-vue": "^5.0.0",
"eslint": "^9.0.0",
"jsdom": "^26.0.0",
"msw": "^2.8.2",
"prettier": "^3.5.3",
"typescript": "^5.3.0",
"vite-plugin-dts": "^4.5.3",
"vite": "^5.1.0",
"vite-plugin-dts": "^4.5.3",
"vitest": "^3.1.1"
},
"peerDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/hooks/use-event-stream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Options, StreamResult } from "../types";
import { EventStreamOptions, EventStreamResult } from "../types";

const dataPrefix = "data: ";

Expand All @@ -23,8 +23,8 @@ export const useEventStream = (
onMessage = () => null,
onComplete = () => null,
onError = () => null,
}: Options = {},
): StreamResult => {
}: EventStreamOptions = {},
): EventStreamResult => {
const sourceRef = useRef<EventSource | null>(null);
const messagePartsRef = useRef<string[]>([]);
const eventNames = useMemo(
Expand Down
Loading