Forms
Forms on websites are critical and often the main piece of user interactivity, so forms in SocialStack are intended to be as flexible as possible, without reinventing too much. The Input react component is very commonly used through forms and it can also be extended.
The <Form> React component is a very thin wrapper over <form> in order to add some common conveniences, such as using the submitForm function instead of the default browser page load form submit.
Form example[edit | edit source]
import Form from 'UI/Form';
import Input from 'UI/Input';
...
<Form action="user" submitLabel="Create a user" successMessage="User created!" failureMessage="Unable to create a user now">
<Input type="text" name="email" label="Email Address" validate={["Required"]} />
<Input type="password" name="password" label="Password" />
</Form>
The action of a form is the API endpoint that you would like to use. It is prefixed with /v1/ for you automatically. This means if you want to create an entity, the action is simply the name of the entity (user, page, locale etc). If you want to edit an entity, the endpoint is the name of the entity followed by a forward slash and its ID:
import Form from 'UI/Form';
import Input from 'UI/Input';
...
// Edit user #14
<Form action="user/14" submitLabel="Create a user" successMessage="User created!" failureMessage="Unable to create a user now">
<Input type="text" name="email" label="Email Address" defaultValue={user.email} validate={["Required"]} />
<Input type="password" name="password" label="Set a new password" />
</Form>
Note that create and edit are often almost identical. It is usually a good idea to make one component which can either create or edit depending on if it is given the object to edit or not.
Sometimes a custom endpoint will be created such as for logging in. As the action is just an endpoint name then the url of custom endpoints can be used as a form action too, provided it is expecting values to be POSTed to it of course.
Forms without an action[edit | edit source]
If you'd like to have a form where javascript exclusively handles the submitted response, simply create the form without an action and you can use onSubmitted to perform any processing you need. If you return a promise, the form will remain in the loading state. As with all other forms, onSuccess or onFailed will still occur depending on how that promise resolves. If onSubmitted does not return a promise, onSuccess always triggers provided the form contents were valid.
<Form submitLabel="Do the thing Javascript" onSubmitted={(submittedValues) => {
console.log("The name: ", submittedValues.name);
// return ..optionally something that results in a promise, such as webRequest or Promise.all etc..
}}>
<Input label='Name' name='name' />
</Form>
Input[edit | edit source]
The Input component from UI/Input is just a very thin wrapper over html input with common conveniences built in like a label and validation. It also exists as an extension point as custom Input types can be specified and Input will then route it through to the specified extension.
<Input type="text" name="email" label="Email Address" validate={["Required"]} />
Validation[edit | edit source]
The validate prop provides an array of validation functions. Each function can be an actual js function or a name of a module.
validate={["Required"]}
validate={["UI/SomeCustomValidator"]}
validate={[(value) => {
// do nothing if the value is valid. Otherwise, return an error object (more on those below).
if(!value){
return {
error: 'EMPTY',
ui: 'This field is required'
};
}
}]}
If a string is provided, it will be treated as a module name. If that module name does not contain a forward slash, it will be prefixed with UI/Functions/Validation/. For example, these two are entirely the same:
validate={["Required"]}
validate={["UI/Functions/Validation/Required"]}
As with all module names, the word ThirdParty is ignored. This means you can make use of this shortened form for both thirdparty and firstparty custom validation modules by creating UI/Source/Functions/Validation and putting your custom validation functions in there.
Validation functions themselves - either inline or from a module - are relatively simple: they should return nothing if the value is valid, and an error object if it is invalid. The error object must specify at least 2 fields - a non-localised error code and a localised message for the UI. Once you publish an error code you must not change it, as it exists for other functions to check for a particular error without needing to awkwardly read potentially localised user facing error messages. Here is the contents of the "Required" validator function:
value => {
if(!value || (value.trim && value.trim() == '')){
return {
error: 'EMPTY', // machine readable, do not translate
ui: `This field is required` // User readable, translate this. Also supports JSX
};
}
}
Props[edit | edit source]
This is the main available props for the <Form> react component.
Name | About | Required? | Default value |
---|---|---|---|
onValues | This callback function can be used to manipulate what is actually sent to the server. It is given the complete set of field values, and you must return the values that you would like to submit, or a promise which resolves to that. If you return a promise rejection, the form will display the error - this can be useful for validation particularly where validation depends on multiple field values. Typically this is just by returning the object you were given, after adding/ changing fields on it. | No | none |
successMessage | A JSX supporting message which will appear in a success alert when a 200 response is received. A common convenience piece. | No | none |
failedMessage | A JSX supporting message which will appear in an error alert when a non 2xx response is received. A common convenience piece. | No | none |
action | The URL where the form will be submitted. By default this is API relative, meaning action="user" is going to POST to /v1/user | Yes | none |
submitLabel | If specified, an <Input type='submit' /> will be added at the end of the form with the given text on it. Also a common convenience. | No | none |
onSuccess | A callback which runs with the response, typically from the API. | No | none |
onFailed | A callback which runs with a failure message. This is often from the API, but can be client generated when the failure is down to the a timeout. | No | none |
Submitting a form[edit | edit source]
There are two ways of submitting a form. Either:
- Add any <input type='submit' /> button inside your form
- Obtain a reference to the dom <form> element and call .submit(); on it
How form submit works[edit | edit source]
- Whenever a form is submitted via any method, submitForm is called. This function is from UI/Functions/SubmitForm and can, like all modules, be replaced with a site/ project specific implementation.
- All the <input> fields inside the form are collected.
- If the input DOM element has an onValidationCheck function on it, it will be called. Note that this is where <Input> checks itself. This function should return true if the validation failed.
- If the input DOM element has an onGetValue function on it, that function will be called and whatever it returns will be used as the value to submit. onGetValue is very useful if you have a custom input type which needs to map its internal state into some value ready to submit in the form. If any onGetValue returns a promise, it will be awaited before continuing.
- If there were any validation errors, the submit halts and onFailed runs.
- Otherwise, onValues is given the complete set of all values collected. If onValues returned a promise, it is awaited before the values are then submitted via a webRequest.