react-tree-stream
v1.2.0
Published
Stream React trees recursively, LLM-style progressive rendering.
Downloads
55
Maintainers
Keywords
Readme
React Tree Stream
A flexible React component for creating "streaming" or "typewriter" effects on your content. It walks your React tree sequentially, streaming text over time and treating elements as atomic units in the flow.
Features
- Text Streaming: Renders text content word-by-word or character-by-character.
- Element Sequencing: Plain React elements count as single units in the stream.
- Nested Streams: Supports nesting
TreeStreamcomponents, waiting for each to complete before continuing. - Customizable: Control streaming speed, timing, and the underlying HTML element.
- Observability: Subscribe to run and unit lifecycle callbacks for diagnostics or orchestration.
- Callbacks:
onCompleteevent fires when the entire stream is finished. - Dynamic Content: Automatically restarts the stream if its children change.
- Styling Hooks: Provides
data-streaminganddata-completeattributes for easy CSS styling. - Type-Safe: Fully typed with TypeScript, including props for the underlying element.
Installation
npm install react-tree-stream
# or
yarn add react-tree-streamUsage
Basic Example
Wrap your content with TreeStream to start streaming.
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 tokenized and streamed over time, while plain React elements appear as atomic units when their turn is reached.
import { TreeStream } from 'react-tree-stream';
const MyComponent = () => (
<div style={{ padding: '1rem', background: '#eee' }}>I am a component!</div>
);
function App() {
return (
<TreeStream speed={1} interval={80}>
Here is some text.
<MyComponent />
And here is some more text that will appear after the component.
</TreeStream>
);
}In this example:
Here is some text.streams according tostreamBy<MyComponent />appears as one sequenced unit- the trailing text resumes after that element
Nested Streams
You can nest TreeStream components. A nested TreeStream is one unit in the parent flow, but it can stream its own contents internally. The parent stream pauses and waits for the nested stream to complete before it continues.
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.
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>
);
}Mental Model
TreeStream turns its direct children into a linear execution plan:
- Plain text becomes streamed text tokens
- Plain React elements become one atomic sequenced unit
- Nested
TreeStreamelements become one sequenced unit that may stream internally
This means you control grouping through normal React structure:
- Want several things to appear together: wrap them in a single non-
TreeStreamelement - Want sibling elements to appear one-by-one: render them as siblings
- Want an element's own contents to stream: render that subtree as
TreeStream
Example:
<TreeStream speed={1} interval={50}>
<a>Some link</a>
<a>Another link</a>
<TreeStream as="strong">Emphasized text</TreeStream>
And then some trailing text
</TreeStream>With this setup:
- each
<a>counts as one unit in the parent stream <TreeStream as="strong">counts as one parent unit, but streams inside the<strong>- the trailing text streams after those units are reached
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, andstylereach the node. - Fragments:
<TreeStream as={React.Fragment}>renders no wrapper. In this mode, DOM-specific props likeclassNameandstyleare disallowed and thedata-*attributes are not rendered (no wrapper node).
Example custom wrapper:
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:
<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 to advance per tick. Units are words, characters, plain elements, or nested TreeStream boundaries. |
| interval | number | 50 | The delay in milliseconds between rendering ticks. |
| autoStart | boolean | true | If true, the stream starts automatically on mount. If false, it waits for autoStart to become true. |
| onStart | () => void | | Called when a new stream run starts. |
| onUnitStart| (event) => void | | Called when a unit begins processing. |
| onUnitComplete | (event) => void | | Called when a unit completes processing. |
| 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. |
onUnitStart and onUnitComplete receive { unitIndex, unit }, where unit.type is one of text_stream, instant_render, or nested_stream.
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.
/* Your CSS file */
[data-streaming='true']::after {
content: '▋';
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}