@daohaus/form-builder
DAOhaus apps utilize many forms, and we decided to build a Form Builder tool that can be used to scaffold React forms. Form Builder is designed to take in a form configuration and then leverages existing UI elements and other scaffolding to output a fully functional form component.
This library is highly composable and declarative, and utilizes form elements from our UI library. Form Builder pairs well with Tx Builder to reduce much of the complexity involved in creating forms from scratch. We utilize Form Builder and Tx Builder in our apps where users need to input data for a transaction.
Github (opens in a new tab)
Related Packages
NPM (opens in a new tab)
Usage
Installation
yarn add @daohaus/form-builder
How to add to you application
import { FormBuilder } from '@daohaus/form-builder';
import { MolochFields } from '@daohaus/moloch-v3-fields';
import { BASIC_PROPOSAL_FORMS } from '@daohaus/moloch-v3-legos';
import { TARGET_DAO } from '../targetDao';
export const FormPage = () => {
return (
<FormBuilder
form={BASIC_PROPOSAL_FORMS.TRANSFER_ERC20}
targetNetwork={'0x5'}
customFields={MolochFields}
/>
);
};
Examples
Here is a tutorial on how to build a form.
Components
Form Builder leverages form components from our UI library. It's possible to add custom components to a Form Builder instance, but the default inputs and other elements commonly used in a form (such as title, subtitle, description) utilize DAOhaus component designs.
Since Form Builder is an integrated library, the default input components include our learnings from building forms over the years.
Here are some of the best practices utilized:
Inputs use forwardRef
so that the reference to the element is maintained (when passing from React Hook Form (opens in a new tab)) to the input.
- User events need these
After getting the input, it's styled with styled-components.
input.tsx
with types at the topinput.styles
input.stories
These all go together into the folder for each form element.
These then move into the wrapped input version, which wraps the input with the FieldWrapper
which pulls in quite a lot from RHF (such as the helper text, required asterisks, etc)
We have a Buildable
generic that adds the FieldWrapper
and PrimitiveWrapper
.
Info prop receives a string and renders a tooltip.
hasRules
is the validation -- every form lego can accept the rules for validation.
- Receives RHF validation rules, but can also pass in custom values
CoreFieldLookup
is for the FormBuilder
and is the factory for all the supported inputs.
- New official DH inputs can be added to the
CoreFieldLookup
- This would pull it into
FieldLego
and ensure that the types match the values - Data used can only be props of the mapped component, which helps for typing the fields
- This would pull it into
Development Guidelines
When building a component, need to consider if its a form element that takes in data and if so you'll want to wrap in the Buildable
- Can look at the other wrapped components as examples
- Get the
useForm
context and registers everything
- Get the
Since folks may want just the field we create the wrapped and unwrapped versions.
Composing elements example:
CSInput
wraps WrappedInput
and watches everything that comes in to this input. It creates helper text and slices things up via the setValueAs
. This slices up things that are separated by commas and turns them into an array of strings.
- Gets props from the WrappedInput and adds 1 thing
Can keep wrapping and layering in the functionality.
Can folks overwrite rules if they don't want certain aspects?
We need consistent behavior, so it's tricky to overwrite -- the higher level rules take precedent so someone could add to the lower level rules or create their own wrapper (such as doing a slash separated input instead of a comma separated one)
Like a pyramid -- higher level up is more specific and opinionated.
WrappedInput
won't be setting its own rules.