SeqFlowJS

Simplicity is the key

Async client components

SeqFlow Component are asynchronous functions.

Your component can handle async operations in a clear way, just using await operator.

import { Contexts, ComponentProps, start } from '@seqflow/seqflow';

// component properties
interface MyComponentProps {
  loadingText?: string;
}

// A simple component function
export async function MyComponent(
  // component properties
  { loadingText }: ComponentProps<MyComponentProps>,
  // component context
  { component }: Contexts
) {
  // Render loader
  component.renderSync(<div>{loadingText ?? 'Loading...'}</div>);
  const data = await fetch('https://quotes.seqflow.dev/api/quotes/random').then(
    async (res) => ({
      statusCode: res.status,
      body: await res.json(),
    })
  );

  // Redraw the whole component
  component.renderSync(
    <pre>
      <code>{JSON.stringify(data, null, 2)}</code>
    </pre>
  );
}

start(document.getElementById('root')!, MyComponent, {}, {});See live example

Event as event stream

SeqFlow component uses modern Javascript statements like for await to handle events

import { Contexts, ComponentProps, start } from '@seqflow/seqflow';

async function MyComponent(
  {}: ComponentProps<unknown>,
  { component }: Contexts
) {
  component.renderSync(
    <button key="my-button" type="button">
      Click me
    </button>
  );

  // create AsyncGenerator
  const events = component.waitEvents(component.domEvent('my-button', 'click'));
  // Wait for events
  for await (const ev of events) {
    window.alert('Button clicked: ' + ev.type);
  }
}

start(document.getElementById('root')!, MyComponent, {}, {});See live example

Javascript variable as state

SeqFlow uses simple Javascript variable to store component data.

You can use number, string, array, object or class instances, as simple as it should be.

import { Contexts, ComponentProps, start } from '@seqflow/seqflow';

async function MyComponent(
  {}: ComponentProps<unknown>,
  { component }: Contexts
) {
  // The state is a simple Javascript variable
  let counter = 0;

  component.renderSync(
    <button key="my-button" type="button">
      Click me
    </button>
  );

  const events = component.waitEvents(component.domEvent('my-button', 'click'));
  for await (const ev of events) {
    // Update the counter
    counter++;
    window.alert('Number of click:' + counter);
  }
}

start(document.getElementById('root')!, MyComponent, {}, {});See live example

Explicit updates

SeqFlow component can re-render the whole component or just perform a partial update

You can name a child to refer to it later

import { Contexts, ComponentProps, start } from '@seqflow/seqflow';

async function MyComponent(
  {}: ComponentProps<unknown>,
  { component }: Contexts
) {
  component.renderSync(
    <>
      <button key="my-button" type="button">
        Now
      </button>
      <div key="counter">{new Date().toISOString()}</div>
    </>
  );

  const events = component.waitEvents(component.domEvent('my-button', 'click'));
  for await (const ev of events) {
    // Replace the counter div element
    component.replaceChild('counter', () => (
      <div key="counter">{new Date().toISOString()}</div>
    ));
  }
}

start(document.getElementById('root')!, MyComponent, {}, {});See live example

"Talk is cheap. Show me the code."
Linus Torvalds

// Imports
import { Contexts, ComponentProps, start } from '@seqflow/seqflow';
import { Button } from '@seqflow/components';
import '@seqflow/components/style.css';

interface CounterProps {
  initialValue?: number;
}

// Counter component function
async function Counter(
  // component properties
  { initialValue }: ComponentProps<CounterProps>,
  // component context
  { component }: Contexts
) {
  let counter = initialValue || 0;

  // Render
  component.renderSync(
    <>
      <Button key="increment-counter-button">Increment</Button>
      <div key="counter">{counter}</div>
    </>
  );

  // create AsyncGenerator
  const events = component.waitEvents(
    // listen "click" event on element tagged by the 'increment-counter-button' key
    component.domEvent('increment-counter-button', 'click')
  );

  // Wait for events
  for await (const _ of events) {
    counter++;
    // Replace a child by key
    component.replaceChild('counter', () => <div key="counter">{counter}</div>);
  }
}

start(document.getElementById('root')!, Counter, {}, {});See live example
// Imports
import { Contexts, ComponentProps, start } from '@seqflow/seqflow';
import { Loading } from '@seqflow/components';
import '@seqflow/components/style.css';

// Quote interface
interface Quote {
  author: string;
  content: string;
}
// Pure function to fetch a random quote
async function getRandomQuote(): Promise<Quote> {
  const res = await fetch('https://quotes.seqflow.dev/api/quotes/random');
  if (!res.ok) {
    throw new Error('Failed to fetch quote');
  }
  return await res.json();
}
// RandomQuote component function
export async function RandomQuote(
  // component properties
  {}: ComponentProps<unknown>,
  // component context
  { component }: Contexts
) {
  // Render
  component.renderSync(<Loading />);

  // Async invocation inside the component
  let quote: Quote;
  try {
    quote = await getRandomQuote();
  } catch (error) {
    component.renderSync(<div>Error: {(error as Error).message}</div>);
    return;
  }

  component.renderSync(
    <blockquote>
      <p>{quote.content}</p>
      <footer>{quote.author}</footer>
    </blockquote>
  );
}

start(document.getElementById('root')!, RandomQuote, {}, {});See live example