#
Megaflow
Would you like to create simple and efficient workflows inside your app, by providing only JSON schema? This repo contains solution for your problem.
#
What is it?
MegaFlow is a React component dedicated to build workflows based on JSON schemas, powered by React Hook Forms.
As there are multiple workflows to generate in the company, which all of them are custom, also can be modified, we decided to have a component which will be used across all of our projects, so that it is possible to keep consistency along them.
#
Main libraries/technologies used in MegaFlow:
Below you can find main libraries/technologies which we have used in Megaflow. All other libraries are used by basing on design needs. Moreover, there is an example (story) which shows how this component works, based on provided schema in Storybook.
Storybook Shortly, it is a development playground. We use it as an environment for components development. Also we build and deploy it to Github Pages as a showcase for the components we have.
Styled-Components CSS-in-JS library which we use to make style neatly arranged through every component
Material-UI Most of the components are built on top of MUI, it lets us save significant amount of time, since we do not need to write everything from scratch
React-Hook-Form Form elements are implemented in a way to be compatible with RHF, as it's one of the best form libraries for React.
#
How to use Megaflow:
- Like you do for any other JS lib or package, just run
yarn add @licenserocks/mega-flow
- Import the component
<MegaFlow />
in your app and use that like in example below
import React from "react";
import schema from "./sample.json";
export const App = () => {
return (
<>
<MegaFlow schema={schema} />
</>
);
};
Part of schema example
{
"steps": [
{
"rows": [
{
"label": "Title",
"fields": [
{
"name": "title",
"required": "Name is required",
"placeholder": "Name of your listed image"
}
]
}
],
"title": "License Metrics"
}
]
}
Good practce: Visit a storybook for this project, and check which props you can pass to imported component 👇 https://licenserocks.github.io/mega-flow
#
Props 👨🔧
- defaultValues 👉 values rendered as default in provided schema
- icons 👉 additional icons which are included in form
- schema 👉 json file, the core of a form
- onFinish 👉 function executed at the end of a form
- onStepSubmit 👉 function executed on concrete step
- renderActionButtons 👉 additional buttons passed to the end of a form
- theme 👉 theme variables used in styling part
- watcher 👉 function which is executed to show declared variables in
inspect mode
- watchList 👉 array of string variables shown in
inspect mode
- wizardProps 👉 props passed directly to
Wizard component
- wrapperProps 👉 props passed directly to
Wrapper component
#
How MegaFlow works ✍️
There is a sample schema file in src
folder which is named sample.json
and it is used in the Storybook for testing and developing purposes. You can also refer to it to see how the schema works in different situations.
MegaFlow parses provided schema file and builds form basing on this data. Below is the most fundamental example of parsing JSON 👇
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count);
// expected output: 42
console.log(obj.result);
// expected output: true
Returning to MegaFlow parsing, this process is made by getting firstly, entire schema
as parsedchema
, then steps
data is isolated from it, so that it is possible to render correctly complex forms with multiple steps.
// Parse if schema was type of JSON string
const parsedSchema = typeof schema === "string" ? JSON.parse(schema) : schema;
const { steps } = parsedSchema;
How MegaFlow displays form based on parsed schema?
Basing on {steps}
generated from parsedSchema
, imported Form
component is included in function renderForm
,
import { Form } from "./components";
const [wizardData, setWizardData] = useState({});
const stepFormData = wizardData[currentStep] || defaultValues;
const stepsArray = steps.map((st) => ({
title: st.title,
}));
const renderForm = () => (
<Form
data={steps[currentStep]}
key={currentStep}
stepIndex={currentStep}
stepFormData={stepFormData}
defaultValues={defaultValues}
/>
);
which is passed further to Wizard
component as currentStepContent
. There was also created an array which contains steps titles, so that it can be easily displayed in Wizard
<Wizard
currentStepContent={renderForm()}
currentStepIndex={currentStep}
renderActionButtons={() => renderActionButtons(getOutputData(wizardData))}
setCurrentStepIndex={setCurrentStep}
steps={stepsArray}
{...wizardProps}
{...props}
/>
Imported Form
component is build mostly on Rockskit library, it seperates from passed data
concrete rows
and renders it in similar way as renderForm
function.
To collect data from current step, MegaFlow uses wizardData
variable which is passed to Wizard
component. This component is imported from rockskit
.
const [currentStep, setCurrentStep] = useState(0);
const isCurrentLastStep = currentStep === steps.length - 1;
const [wizardData, setWizardData] = useState({});
const stepFormData = wizardData[currentStep] || defaultValues;
currentStep
is set to indicate first step, to start proceeding form at the beginning
stepFormata
is getting values from wizardData
at concrete step or passed defaultValues
const getOutputData = (output) =>
Object.values(output).reduce((obj, acc) => ({ ...obj, ...acc }), {});
const onSubmit = (data) => {
const currentState = {
...wizardData,
[currentStep]: data,
};
// Set step data in global wizard object
setWizardData(currentState);
if (!isCurrentLastStep) {
setCurrentStep((prev) => prev + 1);
} else {
onFinish(getOutputData(currentState));
}
};
Submitting is executed by a function onSubmit
, this function firstly, declares constant variable currentState
which contains previously passed wizardData
and data
collected from current step. Then wizardData
is updated end form displays next step or executes onFinish
function.
#
Workflow's features ⛲:
#
Editing Markup options in workflows
In Megaflow is implemented a markup for some inputs through workflow. For example in secret phrase, it is possible to type text, which is bold, italic and even linked. To achieve the wanted result, just follow rules described below:
- Italic -
*some italic text*
- italic text between * - Bold -
**some bold text**
- bold text between ** - Links -
[linked text](https://www.licenserocks.de)
- link in format[linked text](url)
#
How to create a correct schema? 🙋♂️
As it can be seen in How it works
part, schema should be divided into steps
and then rows
. It is the way how it is rendered and displayed, depending on the content of each row. MegaFlow using Wizard
component shows each row content.
Let's look deeper on example json schema.
{
"steps": [
{
"rows": [
{
"hint": "Supported file types: image/* (jpg, png, jpeg) - Max size: 2MB",
"label": "Cover",
"fields": [
{
"name": "cover",
"type": "fileUpload",
"accept": "image/*",
"multiple": false,
"required": "You must upload image cover"
}
],
"labelAlign": "start",
"labelGutter": true
}
],
"title": "Media Assets"
},
{
"rows": [
{
"label": "Title",
"fields": [
{
"name": "title",
"required": "Name is required",
"placeholder": "Name of your listed image"
}
]
}
],
"title": "License Metrics"
}
]
}
To understand how rows
are rendered into exact form, it is neccessary to dive into Wizard
component, which is imported from Rockskit package 👉 https://github.com/LicenseRocks/rockskit and /Form
folder in MegaFlow
Displayed content in Wizard
is declared as a component WizardStepContent
, it is shown below 👇
const content = (
<WizardStepContent
content={steps[currentStepIndex]?.content || currentStepContent}
//other props
/>
);
Like it is coded, content is declared as an array of steps
content attribute or currentStepContent
passed before as a function renderForm()
Declared above content
as WizardStepContent
is executed in the place which depends on orientation
prop, passed to Wizard
as wizardProps
. By default it has a value horizontal
If the place of rendered form is known (how it depends of orientation prop), let's jump into Form
component 👇
const Form = ({ data, defaultValues, stepIndex, stepFormData }) => {
const renderRows = (index) => (
<FormRows
data={data}
index={index}
isRecurring={isRecurring}
rows={data.rows}
stepIndex={stepIndex}
stepData={stepFormData}
/>
);
//further part of a file
Data converted into a form is passed by props stepFormData
and data
into renderRows
function, mainly into FormRows
component.
FormRows
component renders the data, all rows are mapped and fields are passed to FormField
component and other variables such as hint
, label
displayed to the user as a custom component imported from Rockskit
.
To see how fields are generated, it is crucial to see which fieldType
prop should be passed in schema. It is perfectly and understandable represented in mapFieldTypeToComponent
variable.
const mapFieldTypeToComponent = (fieldType) => {
switch (fieldType) {
case "datepicker":
return FormDatepicker;
case "select":
return Select;
case "borderedRadio":
return BorderedRadio;
case "checkbox":
return Checkbox;
case "radio":
return Radio;
case "toggleSwitch":
return ToggleSwitch;
case "fileUpload":
return FileUpload;
case "filePond":
return FilePond;
case "price":
return PriceField;
case "reactSelect":
return ReactSelect;
case "stepper":
return Stepper;
case "textArea":
return TextArea;
default:
return Input;
}
};
Each represented fieldType
generates proper component from rockskit
and is displayed to the user 😍
To sum up: If you are wondered how to create correct json schema, divide it into steps, then rows and follow Form
and Field
files to pass proper values 😉
#
How to go through process of builiding and packaging MegaFlow? 🧑🏻💻
We use Rollup for this process. It is configured in a way to use Babel to transpile the code and export the package in two formats: ESModules and CommonJS.
Now imagine we want to update a code in one of the components:
- Update the component code and make sure everything is working, at least in Storybook. If needed, update the code of component story in
stories.js
file. - Begin the building process by running
yarn build
and make sure, that build phase passes successfully and we have updated files indist
directory. - Update the version of package either manually or using
npm version
(better to do it manually). - Make sure you have an access to @licenserocks packages using your npm account, then run
npm login
to complete authentication process. - Now run
npm publish
. Keep in mind that you should have configured your npm CLI before running this command. Then usenpm login
to login into your account. For more information about how to login to NPM, visit this link: https://docs.npmjs.com/logging-in-to-an-npm-enterprise-registry-from-the-command-line . - Commit your changes and push to Github repository.
That's all!🚀
#
Running Locally 🏡
To run Rockskit with full view through storybook on localhost, follow steps below 👇
- Clone repo to your machine, by running
git clone <remote url>
- Install dependencies by running
yarn
- Run on your local by
yarn start
You can start modifying MegaFlow now 😉
#
Configuration ⚙️
If it is super important to change some configurations for Babel or Rollup, it can be done carefully by editing rollup.config.js
and babel.config.js
files.