hubspot-form
v0.0.32
Published
An internal form library designed to integrate into our hubspot React Extension UI projects. This package simplifies nested form creation by using a config-driven approach, making it easy to manage and validate forms by scope.
Downloads
876
Readme
OWM hubspot form config
An internal form library designed to integrate into our hubspot React Extension UI projects. This package simplifies nested form creation by using a config-driven approach, making it easy to manage and validate forms by scope.
To Install the package, run:
npm install @hubspot-form
Understanding Form-config concepts
// formState is a dictionary
const formState = {
// user_info is a scope on formState dictionary
user_info: {
fields: {
first_name: createField({
name: first_name,
type: text
}),
// array field
addresses: [
{
fields: {
type: createField({
name: type,
type: select,
options: [
{ label: "Permanent", value: "Permanent" },
{ label: "Mailing", value: "Mailing" }
]
}),
street: createField({
name: street,
type: text,
}),
unit_n: createField({
name: unit_n,
type: text,
required: false,
})
},
// nested scope validation
scope_validation: {
isValid: false,
}
}
],
},
// user_info scope validation
scope_validation: {
isValid: false,
}
}
}
// UI usage
<Scope name="user_info">
<CustomInput name="first_name">
{formState.user_info.fields.addresses.map((address, index) => (
<>
<Scope name="fields.addresses" index={index}>
<CustomInput name="type">
<CustomInput name="street">
<CustomInput name="unit_n">
</Scope>
<Button onClick={() => {
// remove address item
handleDeleteItem(
{
scope: "user_info.fields.",
name: "addresses",
index
}
)
}}>
Remove Address
</Button>
</>
))}
<Button onClick={() => {
handleNewItem(
{
scope: "user_info.fields",
name: "addresses",
data: {}
}
)
}}>
Add new Address
</Button>
</Scope>
<Button
isDisabled={!isValidScope(formState, "user_info")}
onClick={() => {
const { data } = handleSubmit()
// { first_name: "John Doe", addresses: [] }
}}>
Add new Address
</Button>
Understanding FormConfig features
// feature functions:
import {
createField,
handleNewItem,
handleUpdateField,
handleResetScope,
handleDeleteItem,
handleSubmit,
isScopeValid,
} from '@owm-hubspot-form';
// create fields fn is necessary to create a new
// this function will infer FormField type.
first_name: createField({
name: "first_name",
type: "text",
label: "First Name",
defaultValue: contact?.first_name,
validate: (value) => {},
visibility: (state, scope, parent) => {},
rules: {
minDigits: 0,
maxDigits: 0,
isEmai: true
},
}),
// handleNewItem fn will add a item in one array field:
// on this function we are adding a new task on scope "board",
// field > in-progress which is an array of tasks
handleNewItem({
scope: "board",
name: "in-progress",
data: {
task: "new task",
priority: "2",
id: new Date().toString()
}
})
// handleUpdateField is used to update field properties dynamically
// in this example we are updating last_name on scope user_info
// to be dynamically required.
handleUpdateField({
scope: "user_info",
name: "last_name",
data: {
...user_info.fields.middle_name,
required: true,
}
})
// Fn is required to reset the fields values on that particular scope
handleResetScope({
scope: "user_info",
});
// handleSubmit fn will return all your scope values with:
// data: {properties: { first_name: "value" }}
const response = handleSubmit()
// handleInputChange fn will return all your scope values with:
// data: {properties: { first_name: "value" }}
handleInputChange({
scope:, // string
name, // string
value, // fieldValues
});
Creating first Panel Steps
import React, { useEffect, useState } from "react";
import {
FormProvider,
createField,
type FormState,
handleSubmit
} from '@owm-hubspot-form';
const MOCKED_USER_DATA = {
first_name: "John"
middle_name: null,
last_name: "Doe",
banks: [
{
id: new Date(),
institution: "",
account: "",
transit_n: ""
agenc_n: "",
}
]:
}
// Create your first scope config "user_info" to support your mocked data
const getUserInfoFields = (contact: typeof MOCKED_USER_DATA) => {
return {
fields: {
// normal field
first_name: createField({
name: "first_name",
type: "text",
defaultValue: contact?.first_name
}),
// not required field
middle_name: createField({
name: "middle_name",
type: "text",
required: false,
defaultValue: contact?.middle_name
}),
// visibility field by "scope"
last_name: createField({
name: "last_name",
type: "text",
required: false,
defaultValue: contact?.last_name
visibility: (state, scope) => {
// state is your whole form config obj
// scope is your current scope fields "user_info"
return scope.fields.first_name.value
}
}),
// array field on scope
// here you could loop your banks data and returns the array of items
banks: [
{
fields: {
institution: createField({
name: "institution",
type: "text",
}),
account: createField({
name: "account",
type: "text",
})
transit_n: createField({
name: "transit_n",
type: "text",
// example of validation
validate: (value => {
if(value && value?.length >= 6 ) {
return "Value should have max: 6 digits"
}
})
})
agency_n: createField({
name: "agency_n",
type: "text",
validate: (value => {
if(value && value?.length >= 4) {
return "Value should have max: 4 digits"
}
}),
// rules feature is still in dev mode, for now it does not validate the field, you should use validate fn above
rules: {
minDigits: 1,
maxDigits: 5,
isEmail: true,
}
})
},
// Scope validation means that:
// If all Field has value, is visible or it's not required
// we validate the scope.
scope_validation: {
isValid: false,
}
}
],
// This scope relies on "banks" to be validated as banks is part of the scope
scope_validation: {
isValid: false,
}
}
}
}
// configure your scopes types like this:
type FormConfig= {
user_info = ReturnType<typeof getUserInfoFields>;
// you can have more scope as needed
}
// Now, let's display make use of the form on the page.
const MyPanel = () => {
const [formState, setFormState] = useState<FormState<FormConfig>>();
const initFormState = async () => {
// await contact fetching
const response = MOCKED_USER_DATA
// set USER_INFO scope to the state, with pre-filled fields based on response
setFormState({
user_info: getUserInfoFields(response)
});
};
useEffect(() => {
initFormState()
}, [])
return(
<FormProvider<FormConfig> initialConfig={formState}>
<FormContext.Consumer>
{(formContext: IFormContextProps<FormConfig>) => {
const { formState } = formContext;
return (
<>
<Scope name="user_info" index={0}>
<CustomInput name="first_name" />
<CustomInput name="middle_name" />
<CustomInput name="last_name" />
// here you can loop and pass the index on scope
<Scope name="fields.banks" index={0}>
<CustomInput name="institution" />
<CustomInput name="account" />
<CustomInput name="transit_n" />
<CustomInput name="agency_n" />
</Scope>
</Scope>
<Button type="button" isDisabled={!isFormScopeValid(formState,['user_info'])} onClick={async () => {
// handleSubmit will return:
// data: { properties: [field]: value }
const formData = handleSubmit();
await submit(formData)
}}
>
Submit
</Button>
</>
)}}
</FormContext.Consumer>
</FormProvider>
);
};
export default MyPanel;