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-decision-tree-flow

v2.1.0

Published

This is a library to create declarative wizards in react.js and react-native.

Downloads

2,953

Readme

react-decision-tree-flow

This is a library to create declarative wizards in react.js and react-native.

A basic example of a full Wizard looks like this:

export const BasicTree = () => {
  const tree = {
    step1: ['step2'],
    step2: ['step3', 'error'],
    step3: [],
    error: ['step2'],
  };

  return (
    <Wizard tree={tree} first="step1">
      <Step name="step1">
        <div>
          I am step 1
          <br />
          <Controls>
            {({ destinations: { step2 } }) => (
              <button onClick={step2}>Go to Step 2</button>
            )}
          </Controls>
        </div>
      </Step>
      <Step name="step2">
        <div>
          I am step 2
          <br />
          <Controls>
            {({ destinations: { step3, error } }) => (
              <div>
                <button onClick={error}>Go to error</button>
                <button onClick={step3}>Go to Step 3</button>
              </div>
            )}
          </Controls>
        </div>
      </Step>
      <Step name="step3">
        <div>I am step 3. No steps after me!</div>
      </Step>
      <Step name="error">
        <div>
          I am error
          <br />
          <Controls>
            {({ back }) => <button onClick={back}>Go back to Step 2</button>}
          </Controls>
        </div>
      </Step>
    </Wizard>
  );
};

Wizard

The wizard component takes in two props as inputs, a tree and the first step of the wizard. The first step is the initial state of the wizard.

const tree = {
  step1: ['step2'],
  step2: ['step2', 'error'],
  step3: [],
  error: [],
};

const MyWizard = ({ children }) => {
  return (
    <Wizard tree={tree} first="step1">
      {children}
    </Wizard>
  );
};

Defining a tree

Trees are defined using JavaScript objects such as the following

const tree = {
  step1: ['step2'],
  step2: ['step3', 'error'],
  step3: [],
  error: [],
};

Each step is a key. The value for each of those keys are the possible destinations for the objects in the tree. In the above example, step1 for example can go only to step2, and step2 may to to step3 or the error step. An empty array such as that in step3 and error signifies that the Wizard has no possible destinations.

If using TypeScript, I highly suggest using const assertions to allow for hinting of destinations. This may be done by adding as const to your tree object as such.

const tree = {
  step1: ['step2'],
  step2: [],
} as const;

Steps

Inside of a Wizard, one should create Steps. Steps do not have to be displayed linearly. Only one step may be shown at one. If multiple steps in a Wizard have the same name, they may both be shown.

Typically, steps are used like below:

const tree = {
  step1: ['step2'],
  step2: [],
};

const MyWizard = () => {
  return (
    <Wizard tree={tree} first="step1">
      <Step name="step1">
        <span> Hello From Step 1</span>
      </Step>
      <Step name="step2">
        <span> Hello From Step 2</span>
      </Step>
    </Wizard>
  );
};

Controls

Controls are what actually drive the Wizard. They may be surfaced via a render prop or hook.

// Render Prop
<Wizard>
  ...
  <Controls>
    {({ step, tree, destinations: { step2 }, data, back }) => (
      <>
        {data && <p>{data}</p>}
        <button onClick={step2}>Go to Step 2</button>
      </>
    )}
  </Controls>
  ...
</Wizard>;

// Hook
const { step, tree, destinations, data, back } = useControls();

When using the hook in typescript, you may pass a typeof tree in as a generic for useControl() to allow for hinting under destinations as such.

const myTree = {
  step1: ['step2'],
  step2: [],
} as const;

...
const { step, tree, destinations } = useControls<typeof myTree>();
// destinations.step1 and destinations.step2 will be hinted!

The hook and render props deliver 3 things in the return, the current step that the wizard is at, the tree that the wizard is using, and finally the destinations for the wizard.

The only rule surrounding the Controls component and it's hook is that it has the Wizard as a descendant. It may go under a Step or just the Wizard in general.

Data may also be passed from step to step using the functions in destination that data is surfaced here using the data prop. If data is falsey, it will be undefined.

The back function allows for you to step back in the tree. It will bring the user back to the previously visited step and data will return to what it was previously as well. One may pass data into back as a parameter to replace the data that was in there previously with something new if desired.

destinations aka Moving the Wizard.

Inside of the destinations object, you will find keys that correspond to where the wizard can go. The values of those keys are functions that change the state of the wizard. Consider the following example:

const tree = {
  step1: ['step2', 'step3'],
  step2: ['step3'],
  step3: [],
};

const MyWizard = () => {
  return (
    <Wizard tree={tree} first="step1">
      <Step name="step1">
        <span> Hello From Step 1</span>
      </Step>
      <Step name="step2">
        <span> Hello From Step 2</span>
      </Step>
      <Step name="step3">
        <span> Hello From Step 3. The end!</span>
      </Step>
      <Controls>
        {({ step, destinations }) => {
          // At step === step1, destinations will contain { step2: () => void, step3: () => void }
          // At step === step2, destination will only contain { step3: () => void }
          // at step === step3, destination will be an empty object.
          Object.entries(destinations).map(([stepName, goToStep]) => {
            return (
              <button key="stepName" onClick={goToStep}>
                Go to {stepName}
              </button>
            );
          });
        }}
      </Controls>
    </Wizard>
  );
};

Passing data from step to step.

Data may also be passed from step to step using the destination functions. For example:

const tree = {
  step1: ['step2'],
  step2: ['step1'],
};

const MyWizard = () => {
  return (
    <Wizard tree={tree} first="step1" initialData="Hello There">
      <Step name="step1">
        <span> Hello From Step 1</span>
        <Controls>
          {({ destinations: { step2 }, data }) => {
            // On first render, data will be "Hello There"
            // All subsequent will be "Kenobi"
            return (
              <>
                <div>{data}</div>
                <button onClick={step2('General')}> Go to step 2</button>
              </>
            );
          }}
        </Controls>
      </Step>
      <Step name="step2">
        <span> Hello From Step 2</span>
        <Controls>
          {({ destinations: { step1 }, data }) => {
            // Data will always be "General"
            return (
              <>
                <div>{data}</div>
                <button onClick={step1('Kenobi')}> Go to step 1</button>
              </>
            );
          }}
        </Controls>
      </Step>
    </Wizard>
  );
};

Recipes

Effects/Middleware

One may want to put an effect onto the state of a wizard changing. Previously, this library had a middleware function. That has been removed in favor of using useEffect. Consider the following example:

const tree = {
  step1: ['step2', 'step3'],
  step2: ['step3'],
  step3: [],
} as const;

const WizardInternals = () => {
  const { step, destinations } = useControls<typeof tree>();
  React.useEffect( () => {
    console.log(`I can do things with ${step}`)
  }, [step])
  return (
    <>
      <Step name="step1">
        <span> Hello From Step 1</span>
      </Step>
      <Step name="step2">
        <span> Hello From Step 2</span>
      </Step>
      <Step name="step3">
        <span> Hello From Step 3. The end!</span>
      </Step>
      {
        Object.entries(destinations).map(([stepName, goToStep]) => {
          return (
            <button key="stepName" onClick={goToStep}>
              Go to {stepName}
            </button>
          );
        });
      }
    </>
  );
};

const MyWizard = () => {
  return (
    <Wizard tree={tree} first="step1">
      <WizardInternals />
    </Wizard>
  );
};