Vulcan 1.15

Sacha Greif
VulcanJS
Published in
4 min readMay 9, 2020

I’m going to guess that the past few weeks have sucked for pretty much everybody reading this. But in a way, open-source software can be a unexpected escape from the daily grind.

Fixing bugs and refactoring React forms might not sound like the sexiest thing ever, but it’s a nice way to feel like you’re making progress on something and find a little bit of normality in a constantly changing world!

Here’s what’s new in the latest Vulcan release.

Field-Specific Data Loading

A big change in Vulcan 1.15 is how field-specific data loading is handled. This is the kind of data loading that happens when you need to load info related to a specific form field.

For example, a post’s categoriesIds field might require loading a list of all available categories so that the user can pick one of them without having to manually type in its _id.

Previously, this was achieved by assigning a query fragment to each field, then aggregating all these fragments together into one big query that ran when the form was first loaded.

Starting in Vulcan 1.15, each form field is responsible for its own data loading thanks to a new FormComponentLoader component.

While this is less efficient in theory since it replaces a single big query with many small ones, thanks to Apollo’s batching this isn’t really a factor in practice. And it makes the overall architecture much easier to reason about.

BREAKING CHANGE: the query property’s content must now specify a query name.

Before:

postsIds: {
query: `
posts {
_id
title
}
`
}

Now:

postsIds: {
query: `
query PostsQuery {
posts {
_id
title
}
}
`
}

Note that the query can accept one variable, the field’s value:

postsIds: {
query: `
query PostsQuery($value: [String!]) {
posts(input: { filter: { _id: { _in: $value } } }) {
_id
title
}
}
`
}

Handling Empty Variables

Because GraphQL will throw an error if a variable is defined in a query but not provided, you can also pass a function to the query property and have that function return undefined if the value is not defined, which will abort the query:

postsIds: {
query: ({ value }) => value && `
PostsQuery($value: [String!]) {
posts(input: { filter: { _id: { _in: $value } } }) {
_id
title
}
}
`
}

A common use case would be a “new document” form, where form field values will typically be empty. By following the above pattern the query will never run in that scenario.

A note about options

Because of how field-specific data loading is now handled, options function cannot expect to receive data. Watch out for undefined errors:

Wrong:

options: props => props.data.products.results.map(product => ({
value: product._id,
label: product.name,
})), // props.data may be undefined

Right (using Lodash’s get):

options: props => _.get(props, 'data.products.results',   []).map(product => ({
value: product._id,
label: product.name,
})),

Field Decorators

Starting in Vulcan 1.15, we’re trying out a new experimental pattern that we’re calling field decorators.

These are basically “shortcut” functions that you can call on fields to automatically assign the right properties to them for a given input type.

For example, here’s how to use the new makeAutocomplete field decorator to implement the new autocomplete input type:

import { makeAutocomplete } from 'meteor/vulcan:core';const mySchema = makeAutocomplete({
fieldFoo: {
optional: true,
// etc.
}
}, options)

In this case, the second options argument requires the following property:

  • autocompletePropertyName : the name of the property on which to run the autocomplete (e.g. label, title, name, etc.).

If the field is a relation field (e.g. field.resolveAs.relation is defined), makeAutocomplete will try to guess the correct resolver to use based on the relation. If not, you can also manually specify this option:

  • queryResolverName: the name of the multi resolver that will be queried to get the required data (e.g. movies, posts, etc.).

Given those options, makeAutocomplete will add the correct input type as well as generate the queries needed both to load previously selected options and autocomplete suggestions.

Note that this is still a bit experimental and might change in the future based on the feedback we receive. Let us know if this is a good pattern or if you’d prefer something more obvious (but maybe also more “magical”?) like input: "autocomplete" .

Easier Schemas with createSchema()

Vulcan 1.15 introduces the new createSchema() function. This is a smaller wrapper that takes a Vulcan schema and “converts” it into a SimpleSchema schema.

The arrayItem Property

Having this middle layer lets us be a bit smarter about some things. For example, the new arrayItem property lets you specify the type and shape of an array’s items without having to create a separate foo.$ field.

Before:

const mySchema = {
fooArray: {
type: Array,
// ...
},
'fooArray.$': {
type: String,
// ...
}
}

Now:

const mySchema = createSchema({
fooArray: {
type: Array,
arrayItem: {
type: String,
// ...
},
// ...
}
});

Other Improvements

Improved MutationButton

The MutationButton component will now show you any GraphQL errors resulting from the mutation in a small tooltip above the button.

Meteor 1.10.2

We now officially support the latest version of Meteor.

GraphQL Schema Logging

Your app’s GraphQL schema is now automatically logged out to a schema.graphql file at the root of your project every time your code changes.

Update Troubleshooting

If you run into update troubles, let us know in the Slack chatroom.

One common problem is having outdated versions of Meteor packages. Make sure the meteor:apollo package in particular is at version 3.1.0.

--

--

Sacha Greif
VulcanJS

Designer/developer from Paris, now living in Osaka. Creator of Sidebar, VulcanJS, and co-author of Discover Meteor.