Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

🌊 react-flow-z

NPM Downloads

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-z

Basic 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 Flow instance is stateful
  • Operators like tap, catch, onDone mutate the instance
  • Prefer: configuring the flow once, handling React state outside the flow

License

MIT

About

A lightweight async flow runtime for orchestrating side effects with explicit control over cancellation, debounce, throttling, and execution order.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors