npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-dataflow

v0.3.1

Published

A dataflow execution format for React.

Downloads

27

Readme

react-dataflow

A dataflow execution library for React.

It helps you build applications where your components are the business logic; the behaviour of the application arises from the way the components have been connected, like a circuit. This is in stark contrast to conventional React, where well-architected applications usually emphasise a clear separation between presentation and computation elements.

By contrast, react-dataflow is a "data-first" perspective of React; where we directly utilise the React DOM to drive efficient updates using the powerful Top Level API, meanwhile the ability to render an equivalent frontend comes as a happy biproduct.

Instead of propagating data values directly via props, react-dataflow enables you to distribute data indirectly using wires. These permit conventional React components to share data independently of scope, and enables deeply-nested updates in self-managing child components to drive changes towards components anywhere in the hierarchy, without explicit handling. This makes react-dataflow more conducive to describing flow-based computation in React.

🚀 Getting Started

Using npm:

npm install --save react-dataflow

Using yarn:

yarn add react-dataflow

✍️ Tutorial

To use react-dataflow, your top-level application needs to be wrapped with the withDataflow HOC. This injects all of the required dependencies for dataflow-driven execution into your <App />:

import React from 'react';
import { withDataflow } from 'react-dataflow';

const App = ({ ...extraProps }) => (
  <React.Fragment
  />
);

export default withDataflow(App);

Nothing special, right?

Well, dataflow isn't very useful without having sources of data, so let's create one! A great example of a data source is a clock signal, like the ones we find in digital logic circuits. These emit a bistable signal which oscillates between a high and low voltage at a fixed interval, and are useful for enforcing synchronization between distributed tasks.

First, let's see how we'd create one of these signals using conventional React:

import React from 'react';
import { useRaf } from 'react-use';

const DigitalClock = React.memo(
  () => {
    const [ arr ] = useState([false]);
    useRaf(1000);
    return  arr[0] = !arr[0];
  },
  // XXX: Assert that this component
  //      doesn't care about external
  //      props.
  () => true,
);

Here, we use the useRaf hook to repeatedly request smooth animated render frames for our component. On each frame, the component is re-rendered, and we continuously invert the contents of our signalBuffer, which is returned as a child. This generates the output true, false, true, false over and over again.

This helps establish the square wave "form" of data we're interested in using pure React, but let's turn our attention over to some of the restrictions.

Firstly, this is a pretty boring application! All we do is render a constantly flickering boolean string on the DOM when we render a <DigitalClock />. Of course, we could dress this up, but there's a much bigger problem at play; what happens when we want actually want to use the output value to drive a change somewhere else in our DOM? In traditional React, we would have to nest some child components which should be sensitive to these changes, but then our DigitalClock starts to be responsible for a lot more; it stops being just a DigitalClock, and more like a DigitalClockProvider.

Alternatively, we could use a callback function which manipulates the state of our parent, but this causes the parent component to re-render and requires the parent to manage propagation of the signal itself, when it doesn't necessarily require an informed interest in the value of the signal. By contrast, when using dataflow, it's trivial to re-route data between consumers, and allow passed messages to execute asynchronously, independent of the parent state.

To demonstrate these shortcomings, imagine that we wish to connect our <DigitalClock /> compnent to a separate <LightEmittingDiode /> component, which will light up when the clock becomes active:

const LightEmittingDiode = ({ style, active }) => (
  <div
    style={[
      // XXX: Leave this part to your imagination!
      style,
      {
        backgroundColor: active ? 'green' : 'grey',
      },
    ]}
  />
);

How would it be possible to connect the LightEmittingDiode's input active prop to the output of the DigitalClock? Well... we could use a wire:

import React from 'react';
import { LightEmittingDiode, DigitalClock } from './components';
import { withDataflow, useWire } from 'react-dataflow';

// XXX: Here, data is passed along a wire!
const App = () => {
  const wire = useWire();
  return (
    <>
      <DigitalClock
        cout={wire}
      />
      <LightEmittingDiode
        active={wire}
      />
    </>
  );
};

export default withDataflow(App);

Here, the DigitalClock's cout prop is connected to the wire we've created by making a call to the useWire hook. Conversely, the LightEmittingDiode's active prop has also been connected to the same wire. Meaning, that whenever the DigitalClock's cout prop is changed, our LightEmittingDiode is automatically re-rendered using the new value that is sourced by the wire.

An additional benefit to this is that because a wire reference itself is effectively a constant, our top-level <App /> instance is only rendered once, even though our DigitalClock and LightEmittingDiode are constantly re-rendering with each cycle.

Complete Example

In this example, we render a DigitalClock, an Inverter and a LightEmittingDiode. Here, whenever the clk signal goes high, the LightEmittingDiode will become inactive, and vice-versa. This allows our LightEmittingDiode to behave like an "active low" component.

Notice that in order to render an <Export /> component to manage the propagation of your component output props along a connected wire, you are required to specify an exportPropTypes attribute. This enables react-dataflow to efficiently manage, and validate, signal propagation using wires:

import React, { useState, useLayoutEffect } from 'react';
import PropTypes from 'prop-types';
import { useRaf } from 'react-use';

import { useWire, withWires, withDataflow } from 'react-dataflow';

const Clock = React.memo(
  ({ Export }) => {
    const [ arr ] = useState([false]);
    useRaf(1000);
    return (
      <Export
        cout={arr[0] = !arr[0]}
      />
    );
  },
  () => true,
);

Clock.exportPropTypes = {
  cout: PropTypes.bool,
};
const Inverter = ({ Export, input }) => (
  <Export
    output={!input}
  />
);

Inverter.exportPropTypes = {
  output: PropTypes.bool,
};

const LightEmittingDiode = ({ style, active }) => (
  <div
    style={{
      width: 100,
      height: 100,
      backgroundColor: active ? 'green' : 'grey',
    }}
  />
);

// XXX: Components withWires can optionally specify an optional
//      "key" prop, which aids in programmatically interpreting
//      the resultant dataflow diagram.
const WiredClock = withWires(Clock, { key: 'Master Clock' });
const WiredInverter = withWires(Inverter);
const WiredLightEmittingDiode = withWires(LightEmittingDiode);

// XXX: Components wrapped withDataflow are passed a "subscribe" prop,
//      which is used to listen to changes in the dataflow diagram state.
function App({ subscribe }) {
  const clk = useWire();
  const nClk = useWire();
  // XXX: Register to listen to signals *after* the initial layout, 
  //      as the digram requires an initial render pass before any
  //      wiring or value propagation can be properly determined.
  useLayoutEffect(
    () => {
      subscribe(
        (signals, elements) => console.log(JSON.stringify(signals), JSON.stringify(elements)),
      );
    },
  );
  return (
    <div className="App">
      <WiredClock
        cout={clk}
      />
      <WiredInverter
        input={clk}
        output={nClk}
      />
      <WiredLightEmittingDiode
        someOtherPropThatWillBeHandledLikeUsual
        active={nClk}
      />
    </div>
  );
}

export default withDataflow(App);

🔂 Iteration

It is also possible to iterate across react-dataflow diagrams. Iterators effectively suggest a most sensible sequence of elements and wires to step through in order to satisfy the rules of dataflow. For example, the Classical sequence type returns an array of phased execution which follows the rule that an element on the diagram cannot be executed until all of its inputs have been satisfied.

import { Sequences } from 'react-dataflow';

console.log(
  // Signals and Elements can be determined via the subscribe callback.
  new Sequences.Classical(
    signals,
    elements,
  ),
); // Returns an array of consecutive phases of linear execution.

✌️ License

MIT