Forms
The vulcan:forms
package provides a SmartForm
component that lets you easily generate new document and edit document forms.
Features
This package can generate new document and edit document forms from a schema. Features include:
- Error handling.
- Bootstrap-compatible.
- Cross-component communication (prefill a field based on another).
- Callbacks on form submission, success, and failure.
- Support for basic form controls (input, textarea, radio, etc.).
- Support for custom form controls.
- Submission to Meteor methods.
Usage
Example schema:
1 |
|
Creating Forms
New Document
Just pass the collection
or collectionName
props to the SmartForm
component:
1 | <Components.SmartForm |
Edit Document
Same as the New Document form, but also passing the documentId
to edit.
1 | <Components.SmartForm |
Props
Here are all the props accepted by the SmartForm
component:
Basic Props
collection
The collection in which to edit or insert a document.
collectionName
Instead of passing collection
you can pass the name of the collection.
documentId
If present, the document to edit. If not present, the form will be a “new document” form.
fields
An array of field names, if you want to restrict the form to a specific set of fields.
submitLabel
The text inside the submit button of the form.
layout
A layout property used to control how the form fields are displayed. Defaults to horizontal
.
showRemove
Whether to show a “delete document” link on edit forms.
prefilledProps
A set of props used to prefill the form.
repeatErrors
Whether to repeat validation errors at the bottom of the form.
Callbacks
submitCallback(data)
A callback called on form submission on the form data. Should return the data
object as well.
successCallback(document)
A callback called on mutation success.
errorCallback(document, error)
A callback called on mutation failure.
cancelCallback(document)
If a cancelCallback
function is provided, a “cancel” link will be shown next to the form’s submit button and the callback will be called on click.
removeSuccessCallback(document)
A callback to call when a document is successfully removed (deleted).
changeCallback(currentDocument)
A callback called a every change or blur event inside the form.
Fragments
queryFragment
A GraphQL fragment used to specify the data to fetch to populate edit
forms.
If no fragment is passed, SmartForm will do its best to figure out what data to load based on the fields included in the form.
mutationFragment
A GraphQL fragment used to specify the data to return once a mutation is complete.
If no fragment is passed, SmartForm will only return fields used in the form, but note that this might sometimes lead to discrepancies when compared with documents already loaded on the client.
An example would be a createdAt
date added automatically on creation even though it’s not part of the actual form. If you’d like that field to be returned after the mutation, you can define a custom mutationFragment
that includes it explicitly.
Field-Specific Data Loading
Sometimes, a specific field will need specific data in addition to its own value. For example, you might have a category
field on a Post
that stores a category’s _id
, but simply showing users an empty text-field where they can manually type in that _id
isn’t very user-friendly.
Instead, you’ll probably want to populate a dropdown with all your existing categories’ names (and maybe also images, descriptions, etc.) to make it easier to pick the right one. This in turns means you need a way to load all these categories in the first place.
This is where field-level data loading comes in. This gives you an easy way to tell Vulcan Forms that you need an extra bit of data whenever that field is displayed:
1 | categoryId: { |
We’re doing two things here. First, we’re setting the query
property and passing it an additional bit of GraphQL query code that will be executed when the form is loaded.
Because the extra query code calls the categories
resolver, whatever the resolver returns will then be available on props.data
once our data is done loading.
This lets us set the options
property in order to populate our dropdown. Essentially, we’re just translating a list of categories into a list of { value, label }
pairs.
Note that it’s usually a good idea to pass a high limit
to field queries, since you usually want to load the entirety of your collection (since the categoryId
could point to any category in your database). This does mean that this pattern is currently not ideal for collections with large number of items.
Using documentId
Field-specific queries work by adding “extra” query parts to a specially created formNewExtraQuery HoC when inserting new documents; or to the withSingle HoC when editing an existing document.
When editing a document, you can reuse the documentId
in your extra query parts since it will already have been made available to the main query.
For example, you might want to restrict a list of users to those having the ability to moderate a given document:
1 | moderatorId: { |
Of course, you’ll also have to write your own listDocumentModerators
custom resolver that takes in a documentId
argument and returns the corresponding list of users.
Note that although currentUser
is not passed as an argument to your resolvers, it’s available on the context
object on the server.
Context
The main SmartForm
components makes the following objects available as context to all its children:
autofilledValues
An object containing optional autofilled properties.
addToAutofilledValues({name: value})
A function that takes a property, and adds it to the autofilledValues
object.
throwError({content, type})
A callback function that can be used to throw an error.
getDocument()
A function that lets you retrieve the current document from a form component.
Handling Values
The component handles three different layers of input values:
- The value stored in the database (when editing a document).
- The value being currently inputted in the form element.
- An “autofilled” value, typically provided by an other form element (i.e. autofilling the post title from its URL).
The highest-priority value is the user input. If there is no user input, we default to the database value provided by the props
. And if that one is empty too, we then look for autofilled values.
i18n
This package uses React Intl to automatically translate all labels. In order to do so it expects an intl
object to be passed as part of its context. For example, in a parent component:
1 | getChildContext() { |
Alternative Approach
If you prefer, you can also code your own forms from scratch, either using withCreate
, withUpdate
, and withDelete
, or with your own custom mutation HoCs.