react-tree-stream
Version:
Stream React trees recursively, LLM-style progressive rendering.
167 lines (125 loc) • 6.41 kB
Markdown
# React Tree Stream
[](https://badge.fury.io/js/react-tree-stream)
A flexible React component for creating "streaming" or "typewriter" effects on your content. It intelligently renders text, components, and even nested streams sequentially.
## Features
- **Text Streaming**: Renders text content word-by-word, like a typewriter.
- **Component Rendering**: Instantly renders any non-text React components.
- **Nested Streams**: Supports nesting `TreeStream` components, waiting for each to complete before continuing.
- **Customizable**: Control the streaming speed and the underlying HTML element.
- **Callbacks**: `onComplete` event fires when the entire stream is finished.
- **Dynamic Content**: Automatically restarts the stream if its children change.
- **Styling Hooks**: Provides `data-streaming` and `data-complete` attributes for easy CSS styling.
- **Type-Safe**: Fully typed with TypeScript, including props for the underlying element.
## Installation
```bash
npm install react-tree-stream
# or
yarn add react-tree-stream
```
## Usage
### Basic Example
Wrap your content with `TreeStream` to start streaming.
```tsx
import { TreeStream } from 'react-tree-stream';
function App() {
return (
<TreeStream>
This is a simple example of streaming text content. The component will render this sentence
word by word.
</TreeStream>
);
}
```
### Mixed Content
`TreeStream` can handle a mix of text and React components. Text is streamed, while components are rendered instantly.
```tsx
import { TreeStream } from 'react-tree-stream';
const MyComponent = () => (
<div style={{ padding: '1rem', background: '#eee' }}>I am a component!</div>
);
function App() {
return (
<TreeStream>
Here is some text.
<MyComponent />
And here is some more text that will appear after the component.
</TreeStream>
);
}
```
### Nested Streams
You can nest `TreeStream` components. The parent stream will pause and wait for the nested stream to complete before it continues.
```tsx
import { TreeStream } from 'react-tree-stream';
function App() {
return (
<TreeStream>
This is the parent stream. It will pause here...
<TreeStream as="blockquote" speed={10}>
...and this nested stream will run to completion. Once it's done...
</TreeStream>
...the parent stream will resume.
</TreeStream>
);
}
```
### Character-by-Character Streaming
By default, `TreeStream` streams text content word-by-word. You can change this behavior to stream text character-by-character.
```tsx
import { TreeStream } from 'react-tree-stream';
function App() {
return (
<TreeStream streamBy="character">
This text is being streamed one character at a time. You can change the streaming behavior
to be more granular.
</TreeStream>
);
}
```
## Polymorphic `as` prop
TreeStream is polymorphic: you can render it as any element or component using `as?: React.ElementType`.
- Semantic tags: `<TreeStream as="section" role="region">...`
- Custom components: pass your wrapper and forward props to a DOM element so `data-*`, `className`, and `style` reach the node.
- Fragments: `<TreeStream as={React.Fragment}>` renders no wrapper. In this mode, DOM-specific props like `className` and `style` are disallowed and the `data-*` attributes are not rendered (no wrapper node).
Example custom wrapper:
```tsx
const Custom = ({ children, ...rest }: React.PropsWithChildren<React.ComponentPropsWithoutRef<'div'>>) => (
<div {...rest}>{children}</div>
);
<TreeStream as={Custom} className="panel" data-testid="stream">Text</TreeStream>
```
When using `as={React.Fragment}`, omit DOM-only props:
```tsx
<TreeStream as={React.Fragment}>No wrapper here</TreeStream>
```
## API and Props
The component accepts the following props:
| Prop | Type | Default | Description |
| ------------ | ---------------------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `as` | `React.ElementType` | `'div'` | The element or component to render as the root; supports intrinsic tags, custom components, or `React.Fragment`. |
| `children` | `React.ReactNode` | | The content to be streamed. |
| `speed` | `number` | `5` | The number of units (words, characters, or components) to render per tick. |
| `interval` | `number` | `50` | The delay in milliseconds between each rendering tick. |
| `autoStart` | `boolean` | `true` | If `true`, the stream starts automatically on mount. If `false`, it waits for `autoStart` to become `true`. |
| `onComplete` | `() => void` | | A callback function that is invoked when the entire stream has finished rendering. |
| `streamBy` | `'word' \| 'character'` | `'word'`| Determines the granularity of the streaming. Use `'word'` for word-by-word streaming or `'character'` for character-by-character streaming. |
| `...rest` | `ComponentPropsWithoutRef<typeof as>` | | Any other props are forwarded to the chosen element/component. For `React.Fragment`, DOM-only props are not allowed. |
## Styling
The root element rendered by `TreeStream` includes data attributes that reflect its current state, which you can use for styling.
- `data-tree-stream`: Always present on the component's root element.
- `data-streaming="true"`: Present while the component is actively streaming text or waiting for a nested stream.
- `data-complete="true"`: Present when the stream has finished.
### Example: Blinking Cursor
You can use these attributes to create a blinking cursor effect that appears only during streaming.
```css
/* Your CSS file */
[data-streaming='true']::after {
content: '▋';
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}
```