A Field Component
Let's have a look at a basic field component from our example plain field set:
const Input = ({
id,
label,
value,
onChange,
onBlur,
onFocus,
error,
placeholder,
isRequired,
isDisabled,
isDirty,
hasFocus,
prefix,
suffix,
secondaryText,
type,
errorRenderer,
...props // additional custom props
}) => {
return (
<div id={id}>
{label ? <label htmlFor={id}>{label}{isRequired ? " *" : ""}</label> : null}
<div>
{prefix ? <span>{prefix}</span> : null}
<input
name={id}
value={value || ""}
placeholder={placeholder}
type={type || "text"}
disabled={!!isDisabled}
required={!!isRequired}
onChange={e => {
if (typeof onChange === "function") onChange(e.target.value);
}}
onFocus={e => {
if (typeof onFocus === "function") onFocus();
}}
onBlur={e => {
if (typeof onBlur === "function") onBlur();
}}
/>
{suffix ? <span>{suffix}</span> : null}
</div>
{secondaryText ? <div>{secondaryText}</div> : null}
{error ? errorRenderer ? errorRenderer(error) : (
<div style={{ color: "red" }}>Please fill out this field!</div>
) : null}
</div>
);
}
Let's first go through the properties:
- id: This is the field id, the same as the key in the forms data structure
- label: The label for the field, important for accessibility
- value: The fields value, a string in this case
- onChange: The required change handler which is used to update the forms data
- onBlur: The on blur handler, which triggers on blur validation
- onFocus: The on focus handler, used to track which field has focus or what was the last field which had focus
- error: The error messages if the field didn't validate
- placeholder: A field placeholder, this one is specific to the Input field
- isRequired: A boolean which tells you if the field is required or not
- isDisabled: A boolean which tells you if the field is disabled
- isDirty: A boolean which tells you if the field is dirty, which is the case if the value is different from the initial value
- hasFocus: A boolean for when the field has focus, in case you want to style the field differently on focus
- prefix: A prefix which should be render right before the field in the same line
- suffix: A suffix which should be render right after the field in the same line
- secondaryText: A secondary text which is usually rendered right below the label
- type: The field type, which you only need if the field component renders different field types
- errorRenderer: A custom error rendering component
- ...props: Additional props from the config, this could for example be a custom className if your field set supports those
And a few important things about the form render.
- The
id
prop is used to scroll to the first invalid field on Form actions. If you don't add it as an id on your markup, this functionality will not work. - For full flexibility, always make sure the custom
errorRenderer
is used when available - For some fields,
onChange
andonBlur
will be the same thing, in that case you should still trigger both - As Stages doesn't render any markup, making your form accessible is your job, make sure you set the right properties in your markup!
- If your component doesn't have a default behaviour for being disabled, you still need to make sure it is disabled, this could for example be done like this:
<div
id={id}
style={isDisabled ? {
opacity: "0.3",
pointerEvents: "none"
} : {}}
>
...
</div>
Here's a field which is a little more complex. This field handles both types, select
and multiselect
:
const Select = ({
id,
type,
label,
value,
options,
onChange,
onBlur,
onFocus,
error,
placeholder,
isRequired,
isDisabled,
isDirty,
hasFocus,
prefix,
suffix,
secondaryText,
errorRenderer,
...props // additional custom props
}) => {
return (
<div id={id}>
{label ? <label htmlFor={id}>{label}{isRequired ? " *" : ""}</label> : null}
<div>
{prefix ? <span>{prefix}</span> : null}
<select
name={id}
value={typeof value === "undefined" ? type === "multiselect" ? [] : "" : value}
placeholder={placeholder}
disabled={!!isDisabled}
required={!!isRequired}
multiple={type === "multiselect" ? true : undefined}
onChange={e => {
if (type === "multiselect") {
const options = e.target.options;
const value = [];
for (let i = 0, l = options.length; i < l; i++) {
if (options[i].selected) {
value.push(options[i].value);
}
}
onChange(value);
} else {
if (typeof onChange === "function") onChange(e.target.value);
}
}}
onBlur={e => {
if (typeof onBlur === "function") onBlur();
}}
onFocus={e => {
if (typeof onFocus === "function") onFocus();
}}
>
{options.map(option => <option value={option.value} key={option.value} disabled={option.disabled ? true : null}>{option.text}</option>)}
</select>
{suffix ? <span>{suffix}</span> : null}
</div>
{secondaryText ? <div>{secondaryText}</div> : null}
{error ? errorRenderer ? errorRenderer(error) : (
<div style={{ color: "red" }}>Please fill out this field!</div>
) : null}
</div>
);
}
It is than registered like this to handle both types:
const fields = {
...otherFields,
select: {
component: Select,
isValid: selectIsValid
},
multiselect: {
component: Select,
isValid: selectIsValid
},
};