{"id":28099123,"url":"https://github.com/rjerue/react-decision-tree-flow","last_synced_at":"2025-10-19T03:59:53.333Z","repository":{"id":65483182,"uuid":"174058893","full_name":"rjerue/react-decision-tree-flow","owner":"rjerue","description":"A declarative decision tree / wizard for React and React-Native","archived":false,"fork":false,"pushed_at":"2025-06-24T05:34:22.000Z","size":1573,"stargazers_count":49,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-15T06:36:34.110Z","etag":null,"topics":["multi-step","react","react-native","react-native-web","reactjs","wizard","wizard-steps"],"latest_commit_sha":null,"homepage":"https://rjerue.github.io/react-decision-tree-flow","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rjerue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-06T02:49:27.000Z","updated_at":"2024-10-25T15:22:41.000Z","dependencies_parsed_at":"2023-01-25T11:15:35.202Z","dependency_job_id":null,"html_url":"https://github.com/rjerue/react-decision-tree-flow","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rjerue/react-decision-tree-flow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjerue%2Freact-decision-tree-flow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjerue%2Freact-decision-tree-flow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjerue%2Freact-decision-tree-flow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjerue%2Freact-decision-tree-flow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rjerue","download_url":"https://codeload.github.com/rjerue/react-decision-tree-flow/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjerue%2Freact-decision-tree-flow/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265678930,"owners_count":23810120,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multi-step","react","react-native","react-native-web","reactjs","wizard","wizard-steps"],"created_at":"2025-05-13T17:59:30.602Z","updated_at":"2025-10-19T03:59:48.275Z","avatar_url":"https://github.com/rjerue.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-decision-tree-flow\n\nThis is a library to create declarative wizards in react.js and react-native.\n\nA basic example of a full Wizard looks like this:\n\n```jsx\nexport const BasicTree = () =\u003e {\n  const tree = {\n    step1: ['step2'],\n    step2: ['step3', 'error'],\n    step3: [],\n    error: ['step2'],\n  };\n\n  return (\n    \u003cWizard tree={tree} first=\"step1\"\u003e\n      \u003cStep name=\"step1\"\u003e\n        \u003cdiv\u003e\n          I am step 1\n          \u003cbr /\u003e\n          \u003cControls\u003e\n            {({ destinations: { step2 } }) =\u003e (\n              \u003cbutton onClick={step2}\u003eGo to Step 2\u003c/button\u003e\n            )}\n          \u003c/Controls\u003e\n        \u003c/div\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step2\"\u003e\n        \u003cdiv\u003e\n          I am step 2\n          \u003cbr /\u003e\n          \u003cControls\u003e\n            {({ destinations: { step3, error } }) =\u003e (\n              \u003cdiv\u003e\n                \u003cbutton onClick={error}\u003eGo to error\u003c/button\u003e\n                \u003cbutton onClick={step3}\u003eGo to Step 3\u003c/button\u003e\n              \u003c/div\u003e\n            )}\n          \u003c/Controls\u003e\n        \u003c/div\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step3\"\u003e\n        \u003cdiv\u003eI am step 3. No steps after me!\u003c/div\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"error\"\u003e\n        \u003cdiv\u003e\n          I am error\n          \u003cbr /\u003e\n          \u003cControls\u003e\n            {({ back }) =\u003e \u003cbutton onClick={back}\u003eGo back to Step 2\u003c/button\u003e}\n          \u003c/Controls\u003e\n        \u003c/div\u003e\n      \u003c/Step\u003e\n    \u003c/Wizard\u003e\n  );\n};\n```\n\n## Wizard\n\nThe 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.\n\n```jsx\nconst tree = {\n  step1: ['step2'],\n  step2: ['step2', 'error'],\n  step3: [],\n  error: [],\n};\n\nconst MyWizard = ({ children }) =\u003e {\n  return (\n    \u003cWizard tree={tree} first=\"step1\"\u003e\n      {children}\n    \u003c/Wizard\u003e\n  );\n};\n```\n\n### Defining a tree\n\nTrees are defined using JavaScript objects such as the following\n\n```js\nconst tree = {\n  step1: ['step2'],\n  step2: ['step3', 'error'],\n  step3: [],\n  error: [],\n};\n```\n\nEach 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.\n\nIf using TypeScript, I highly suggest using [const assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to allow for hinting of destinations. This may be done by adding `as const` to your tree object as such.\n\n```ts\nconst tree = {\n  step1: ['step2'],\n  step2: [],\n} as const;\n```\n\n## Steps\n\nInside 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.\n\nTypically, steps are used like below:\n\n```jsx\nconst tree = {\n  step1: ['step2'],\n  step2: [],\n};\n\nconst MyWizard = () =\u003e {\n  return (\n    \u003cWizard tree={tree} first=\"step1\"\u003e\n      \u003cStep name=\"step1\"\u003e\n        \u003cspan\u003e Hello From Step 1\u003c/span\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step2\"\u003e\n        \u003cspan\u003e Hello From Step 2\u003c/span\u003e\n      \u003c/Step\u003e\n    \u003c/Wizard\u003e\n  );\n};\n```\n\n## Controls\n\nControls are what actually drive the Wizard. They may be surfaced via a render prop or hook.\n\n```jsx\n// Render Prop\n\u003cWizard\u003e\n  ...\n  \u003cControls\u003e\n    {({ step, tree, destinations: { step2 }, data, back }) =\u003e (\n      \u003c\u003e\n        {data \u0026\u0026 \u003cp\u003e{data}\u003c/p\u003e}\n        \u003cbutton onClick={step2}\u003eGo to Step 2\u003c/button\u003e\n      \u003c/\u003e\n    )}\n  \u003c/Controls\u003e\n  ...\n\u003c/Wizard\u003e;\n\n// Hook\nconst { step, tree, destinations, data, back } = useControls();\n```\n\nWhen 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.\n\n```tsx\nconst myTree = {\n  step1: ['step2'],\n  step2: [],\n} as const;\n\n...\nconst { step, tree, destinations } = useControls\u003ctypeof myTree\u003e();\n// destinations.step1 and destinations.step2 will be hinted!\n```\n\nThe 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.\n\nThe 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.\n\nData 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.\n\nThe 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.\n\n### `destinations` aka Moving the Wizard.\n\nInside 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:\n\n```jsx\nconst tree = {\n  step1: ['step2', 'step3'],\n  step2: ['step3'],\n  step3: [],\n};\n\nconst MyWizard = () =\u003e {\n  return (\n    \u003cWizard tree={tree} first=\"step1\"\u003e\n      \u003cStep name=\"step1\"\u003e\n        \u003cspan\u003e Hello From Step 1\u003c/span\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step2\"\u003e\n        \u003cspan\u003e Hello From Step 2\u003c/span\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step3\"\u003e\n        \u003cspan\u003e Hello From Step 3. The end!\u003c/span\u003e\n      \u003c/Step\u003e\n      \u003cControls\u003e\n        {({ step, destinations }) =\u003e {\n          // At step === step1, destinations will contain { step2: () =\u003e void, step3: () =\u003e void }\n          // At step === step2, destination will only contain { step3: () =\u003e void }\n          // at step === step3, destination will be an empty object.\n          Object.entries(destinations).map(([stepName, goToStep]) =\u003e {\n            return (\n              \u003cbutton key=\"stepName\" onClick={goToStep}\u003e\n                Go to {stepName}\n              \u003c/button\u003e\n            );\n          });\n        }}\n      \u003c/Controls\u003e\n    \u003c/Wizard\u003e\n  );\n};\n```\n\n#### Passing data from step to step.\n\nData may also be passed from step to step using the destination functions. For example:\n\n```jsx\nconst tree = {\n  step1: ['step2'],\n  step2: ['step1'],\n};\n\nconst MyWizard = () =\u003e {\n  return (\n    \u003cWizard tree={tree} first=\"step1\" initialData=\"Hello There\"\u003e\n      \u003cStep name=\"step1\"\u003e\n        \u003cspan\u003e Hello From Step 1\u003c/span\u003e\n        \u003cControls\u003e\n          {({ destinations: { step2 }, data }) =\u003e {\n            // On first render, data will be \"Hello There\"\n            // All subsequent will be \"Kenobi\"\n            return (\n              \u003c\u003e\n                \u003cdiv\u003e{data}\u003c/div\u003e\n                \u003cbutton onClick={step2('General')}\u003e Go to step 2\u003c/button\u003e\n              \u003c/\u003e\n            );\n          }}\n        \u003c/Controls\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step2\"\u003e\n        \u003cspan\u003e Hello From Step 2\u003c/span\u003e\n        \u003cControls\u003e\n          {({ destinations: { step1 }, data }) =\u003e {\n            // Data will always be \"General\"\n            return (\n              \u003c\u003e\n                \u003cdiv\u003e{data}\u003c/div\u003e\n                \u003cbutton onClick={step1('Kenobi')}\u003e Go to step 1\u003c/button\u003e\n              \u003c/\u003e\n            );\n          }}\n        \u003c/Controls\u003e\n      \u003c/Step\u003e\n    \u003c/Wizard\u003e\n  );\n};\n```\n\n## Recipes\n\n### Effects/Middleware\n\nOne 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:\n\n```tsx\nconst tree = {\n  step1: ['step2', 'step3'],\n  step2: ['step3'],\n  step3: [],\n} as const;\n\nconst WizardInternals = () =\u003e {\n  const { step, destinations } = useControls\u003ctypeof tree\u003e();\n  React.useEffect( () =\u003e {\n    console.log(`I can do things with ${step}`)\n  }, [step])\n  return (\n    \u003c\u003e\n      \u003cStep name=\"step1\"\u003e\n        \u003cspan\u003e Hello From Step 1\u003c/span\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step2\"\u003e\n        \u003cspan\u003e Hello From Step 2\u003c/span\u003e\n      \u003c/Step\u003e\n      \u003cStep name=\"step3\"\u003e\n        \u003cspan\u003e Hello From Step 3. The end!\u003c/span\u003e\n      \u003c/Step\u003e\n      {\n        Object.entries(destinations).map(([stepName, goToStep]) =\u003e {\n          return (\n            \u003cbutton key=\"stepName\" onClick={goToStep}\u003e\n              Go to {stepName}\n            \u003c/button\u003e\n          );\n        });\n      }\n    \u003c/\u003e\n  );\n};\n\nconst MyWizard = () =\u003e {\n  return (\n    \u003cWizard tree={tree} first=\"step1\"\u003e\n      \u003cWizardInternals /\u003e\n    \u003c/Wizard\u003e\n  );\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frjerue%2Freact-decision-tree-flow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frjerue%2Freact-decision-tree-flow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frjerue%2Freact-decision-tree-flow/lists"}