Welcome to the StepOne components documentation
StepOne Fundation
StepOne Modules
Forms
The Form component allows you to simply validate, serialize, automatically set values to the inputs inside it.
The form component uses React context in order to work and for the form to 'be aware' of the inputs inside it. Then it generates a contexts, then when the inputs detect that context and get listed at the form level, so the form will be able to change the state on them.
This way the values passed to a form as a Javascript object can be applied to the fields automatically, but also run the validatios and set them to valid/invalid.
Form examples
Simple form
It's a form on it's simpliest sape, just a form with some inputs that when clicking the submit button serialices its values, check in the code the function 'handleSubmit' to see how it deals with the form submit output
Form with validations, initial values and input change events
This is a form that contains validations, including custom ones and asyncronous ones.
It also sets the initial values passed to the Form component using the "values" property.
And binds any change in any input using the prop 'onChange' of the form.
Validations
The validations are functions that using the value, the form context or the actual input component, apply some logic and return an error or "undefined" if the validation passes succesfully .
Default validations
Along with the shared components for the forms there is a file that contains the most common types of validations: src\components\shared\forms\utilities\validations.tsx
Those validations can be used anywhere just by adding to any input the function as part of the validation array.
In the following example we can see an input with 2 validations: required and url.
<TextInput validations={[required, url]} label="Text input" name="textInput" />Custom validations
You can also create your own custom validation functions. As parameters of the validations by default you can receive: value of the input, context of the input and the input component it self.
const customValidationFunction = (value: any, _context: any, inputComponent: any) => {
if (value !== "Good morning") {
return "Value is wrong, needs to be 'Good morning'";
}
};
<TextInput validations={[customValidationFunction]} label="Text input" name="textInput" />
Asyncronous validations
You are allowed also to use async functions (for instance to call an endpoint to verify an email)
const validEmail = async (value: any, _context: any, inputComponent: any) => {
const emailIsValid = await API.checkEmail(value);
if(!emailIsValid) {
return "Email is already in use";
}
};
<TextInput validations={[validEmail]} label="Text input" name="textInput" />
Multiple fields dependencies
Using the context (parameter passed to all validation functions) you will be able to use multiple fields inside a form the logic of your custom validation function.
For instance you can find another field by name like this: context?.inputs?.find((a: any) => a.props.name === nameToSearch);
export const dateFrom = (value: any, context: any, inputComponent: any) => {
try {
let toDate = null;
let nameToSearch = "";
const nameField = inputComponent.props.name;
if (inputComponent.props.name === "postStartDate") {
nameToSearch = "postEndDate";
toDate = context?.inputs?.find((a: any) => a.props.name === nameToSearch && a._isMounted);
} else {
nameToSearch = nameField.replace("start", "end");
toDate = context?.inputs?.find((a: any) => a.props.name === nameToSearch && a._isMounted);
}
return moment(value, config.date_format, true) > moment(toDate.state.value, config.date_format, true) ? "End date is earlier than start date" : validationIsGood;
} catch (error) {}
};Validate depending on field state or field props
Using the input component you will be able to validate the value depending on the field state or props.
For instance you can check a prop of the field:
export const customValidation = (value: any, context: any, inputComponent: any) => {
inputComponent?.props?.whatever === "whatever" ?? return "error, value is wrong"
}Serialization
Set initial values
You can pass any javascript object to a form, it will automatically pre-set the values in those fields where the name property matches the property name.
<Form values={{ salutation: "Good afternoon", color: ["blue"] }}>
<TextInput label="Salutation" name="salutation" />
<CheckboxInput name="color" label="Color red" value="red" />
<CheckboxInput name="color" label="Color blue" value="blue" />
<input type="submit" value="Submit" />
</Form>Complex object serialization
You can use dots and array shape input names in order to achieve complex and nested object when serializing and adding default values
<TextInput label="Example" name="nestedObject[0].property" />In the following case, the output would be:
{ "nestedObject": [ { "property": "Value example" } ] }Events
Form submit
You can trigger a function when the form submits, passing that function to the onSubmit property of the form.
The parameter passed to that function will be the submit event. See the following example:
const handleSubmit = async (event: any) => {
const isValid_HTML = event.target.isValid;
const values_HTML = serializeForm(event.target);
if (isValid_HTML) {
try {
console.log(values_HTML);
addNotification({ type: "success", content: "Check the console to see the values", timer: 3 });
} catch (err: any) {
addNotification({ type: "error", content: err?.message, timer: 3 });
}
} else addNotification({ type: "error", content: toLiteral({ id: "Check your entries" }) });
};
<Form ref={formRef} onSubmit={handleSubmit}>
<TextInput label="Text Input" name="textinput" type="text" />
<button type="submit">Submit</button>
</Form>When an input changes
You can trigger a function when any of the form inputs change:
const handleInputChange = async (event: any) => {
addNotification({ type: "success", content: "Something has changed!", timer: 3 });
};
<Form ref={formRef} onChange={handleInputChange}>
<TextInput label="Text Input" name="textinput" type="text" />
<button type="submit">Submit</button>
</Form>External methods
Here we'll describe the methods that you can use from outside the form.
Valdiate inputs
You have 2 options to trigger the validation (without triggering the submit).
This will update the state of each input (showing the error or success classes) depending on their value and validation rules.
formRef.current.getValidation(): returns a verbose validation output, describing the errors and components of all inputs.
const isValid = await formRef?.current?.isValid();formRef.current.isValid(): returns true or false.
const validationOutput = await formRef.current.getValidation();Submit a form remotely
You can force a form to submit remotely using it's reference
formRef.current.submit()Reset form (empty the form)
You can force a form to clear all values.
formRef.current.resetFields()Known limitations
Nesting forms
As the forms use the context API, you cannot add a form within another form (anyway this is not valid in HTML either - see W3C documentation)
What you can do is add a modal that renders a form outside this form (usually in the root of the DOM), but for react it would be still inside the same context.
The problem in reality would be that if the modal contains a submit button, even if outside the form, as for react virtualDom is inside the other, would trigger the submit in both forms, the parent and the modal
You need to replace the submit button of the modal by a normal button that triggers the submit.
<button type="button" onClick={() => handleSubmit()}>Save user</button>Here is an example
import _ from "lodash";
import React, { createRef, ReactElement } from "react";
import { toLiteral } from "../../../../helper/locale-utils";
import { Form, TextInput } from "../../../shared/forms";
import { addNotification } from "../../../shared/notifications/notifications-service";
import * as Api from "../../../../api";
import Modal, { ModalBody, ModalFooter, ModalHeader } from "@stepone-ui/modal";
import { email, phone, required } from "../../../shared/forms/utilities/validations";
const UserModal: React.FC<any> = (props): ReactElement => {
const { userModal, closeModal, userFormData } = props;
const handleSubmit = async () => {
const _form = formRef?.current as Form;
const isValid = await _form?.isValid();
const user = _form?.serialize();
if (isValid) {
await Api.updateTenantUser(user);
addNotification({ type: "success", content: toLiteral({ id: "User saved successfully" }), timer: 3 });
}
};
let formRef: any = createRef();
return (
<Modal renderAsPortal open={userModal} onClose={() => closeModal()}>
<Form ref={formRef} values={userFormData}>
<ModalHeader>Add user</ModalHeader>
<ModalBody>
<TextInput name="name" label={"name"} type="text" validations={[required]} />
<TextInput name="email" label={"email"} type="text" validations={[required, email]} />
<TextInput name="phone" label={"phone"} type="text" validations={[phone]} />
</ModalBody>
<ModalFooter className="pt4">
<button type="button" onClick={() => handleSubmit()}>
Save user
</button>
</ModalFooter>
</Form>
</Modal>
);
};
export default UserModal;Buttons unintentionally trigger the form submission
Very often you'll see that a button that is intended to do something, also triggers the form submission.