react-flow-z
v1.0.5
Published
A lightweight async flow runtime for orchestrating side effects with explicit control over cancellation, debounce, throttling, and execution order.
Maintainers
Readme
🌊 react-flow-z
LIVE EXAMPLE
react-flow-z is a small, framework-agnostic async flow runtime.
It focuses on how async logic runs
This library is about orchestration, not reactivity.
Why react-flow-z
- Typed async execution pipeline
- Declarative flow composition
- Abort & cancellation via
AbortController - Async orchestration operators:
debounce·retry·timeout·switchMap·parallel... - Control flow:
filter·take·conditional execution... - Pause / resume execution
- Framework-agnostic core
- Optional React hook (
useFlow)
Installation
npm install react-flow-zBasic Usage
import { Flow } from "react-flow-z"
new Flow()
.debounce(300)
.switchMap(async q => {
const res = await fetch(`/search?q=${q}`)
return res.json()
})
.tap(console.log)
.run("hello")Avoid mutating a shared Flow instance inside React render loops.
Cancellation
const flow = new Flow()
.step(async (v, _, signal) => {
await sleep(1000, signal)
return v
})
flow.run(1)
flow.cancel()Pause / Resume
flow.pause()
setTimeout(() => {
flow.resumeFlow()
}, 1000)React Integration
import { useFlow } from "react-flow-z"
useFlow(
keyword,
flow =>
flow
.debounce(300)
.switchMap(search)
.tap(setResult)
.catch(() => []),
{}
)Search posts (official example)
✅ Pattern 1: Flow instance (recommended)
searchFlow.ts
import { Flow, createFlow } from "react-flow-z";
export type Post = {
id: number;
title: string;
};
export const searchFlow = new Flow<string, Post[]>()
.debounce(300)
.filter(q => q.trim().length > 0)
.switchMap(async q => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?q=${q}`
);
if (!res.ok) throw new Error("network error");
return res.json();
});
// searchFlow.run("r")
// searchFlow.run("re")
// searchFlow.run("react")createFlow.ts
import { Flow, createFlow } from "react-flow-z";
export const searchFlow = createFlow<string, Post[]>()
.debounce(300)
.filter(q => q.trim().length > 0)
.switchMap(async q => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?q=${q}`
)
return res.json()
})React usage
import { useEffect, useState } from "react";
import { searchFlow, Post } from "./searchFlow";
import "./styles.css";
function SearchExample() {
const [q, setQ] = useState("");
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!q) {
setPosts([]);
return;
}
setLoading(true);
setError(null);
searchFlow
.run(q)
.then(result => {
setPosts(Array.isArray(result) ? result : []);
})
.catch(err => {
console.error(err);
setError("Something went wrong");
setPosts([]);
})
.finally(() => {
setLoading(false);
});
return () => {
searchFlow.cancel();
};
}, [q]);
return (
<>
<input
value={q}
onChange={e => setQ(e.target.value)}
placeholder="Search posts..."
/>
{loading && <p>Loading...</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
<ul>
{posts.map(p => (
<li key={p.id}>{p.title}</li>
))}
</ul>
</>
);
}
export default function App() {
return (
<div className="App">
<h2>react-flow-z + React</h2>
<SearchExample />
</div>
);
}
✅ Pattern 2: One-off Flow execution
Submit form – prevent double submit (leading)
import { Flow } from "react-flow-z"
export function runSearch(q: string) {
return new Flow()
.debounce(300)
.filter(Boolean)
.switchMap(async query => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?q=${query}`
)
if (!res.ok) throw new Error("network error")
return res.json()
})
.run(q)
}
Philosophy
- Explicit over implicit
- Async/await over streams
- No global state
- No magic scheduling
- You own execution
Compare high-level
| Point | react-flow-z | RxJS | Redux-Saga | XState | | ---------------------- | ------------ | ----- | ---------- | ------- | | Async orchestration | ✅ | ✅ | ✅ | 🟡 | | Debounce / cancel | ✅ | ✅ | 🟡 | 🟡 | | Execution-first design | ✅ | ❌ | 🟡 | ❌ | | Framework-agnostic | ✅ | ✅ | ❌ | 🟡 | | Learning curve | ⭐ easy | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
What react-flow-z is NOT
- ❌ Not a state manager
- ❌ Not a reactive signal system
- ❌ Not a data cache like React Query
- ❌ Not a stream library like RxJS
If you only need data fetching → use React Query
If you need event streams → use RxJS
If you need explicit async execution with cancel / debounce / queue → react-flow-z
Flow lifecycle notes
- A
Flowinstance is stateful - Operators like
tap,catch,onDonemutate the instance - Prefer: configuring the flow once, handling React state outside the flow
License
MIT
