Let's build the future of forms
with GraphQL 👩🚀 👨🚀
#whoami
Charly POLY - Senior Software Engineer at
Forms on the web ecosystems
- Subject tackled by most ecosystems
- Rails simple_form
- Symfony Forms
- ASP.NET MVC
- Form as a Service
- TypeForm
- Google Forms
What about SPA, JavaScript ecosystem forms?
Role of Forms in a Web Application
Forms handle the experience that user have with data
Developer Experience
User Experience
- Be the most precise as possible
- Make is easy and fast to fill complex informations
- Immediate feedback
- Be flexible, customisable
- Avoid validation divergence with
back-end - Handle state
In short, fast and smooth experience
Current state of JS forms building
Vue.js forms
redux-form
Formik
Benchmark: Vue.js forms
What it doesn’t solve:
- almost everything is done by hand ✍️
- components, fields, state
- no Form state management
- no easily reusable ☠️
- non-standard validation format ☠️
What it solves:
- smart binding
- state related features ( lazy, numbers and debounce attributes )
- (validations using vee-validate)
Benchmark: redux-form
What it doesn’t solve:
- lot of configuration ✍️
- validators by hand ✍️
- forms state in global state ☠️
- Field by Field building workflow ✍️
What it solves:
- Error management
- Form state management
- Built-in components and helpers
The best way to manage your form state in Redux.
Benchmark: Formik
What it doesn’t solve:
- lot of configuration ✍️
- validators by hand ✍️
- non-standard validation format ☠️
- Field by Field building workflow ✍️
What it solves:
- Object-schema based validations with Yup
- Form state management (not global)
- "Functional" state management + render props pattern
- Built-in components and helpers
Build forms in React, without the tears 😭
Benchmark results
Vue.js forms | redux-form | Formik | |
---|---|---|---|
Manage state | ✍️ | ✅ | ✅ |
Data validation | ✍️ | ✍️ / ✅ | ✅ |
Theming | ✍️ | ✍️ | ✍️ |
Error messages | ✍️ | ✍️ | ✍️ |
Fields conf. | ✍️ | ✍️ | ✍️ |
Developer Experience
Benchmark results
Vue.js forms | redux-form | Formik | |
---|---|---|---|
Productivity | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Performance | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
Flexibility | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
Components Reusability |
⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Validation Isomorphism |
nodejs-only | nodejs-only | nodejs-only |
Architecture
Observation
-
No matters the solution, we have to duplicate:
- fields list and definition
- data validations
What about types?
Since forms are about data, why not build typed forms?
The JavaScript eco-system new era:
- TypeScript: typed JavaScript
- GraphQL: typed data exchange
Feedback: Dropping redux-form
configured
redux-form
class Form extends ModuleForm {
fields: FieldsDefinitions = {
id: 'none',
email: 'none',
picture_path: {
type: 'image', transformations: 'h_200,w_200,r_max,c_fill'
},
first_name: 'string*',
last_name: 'string*',
username: 'string*',
job_title: 'string',
company_name: 'string',
language: {
type: 'select*',
component: LanguageSelectView,
valueProperty: 'code',
values: supportedLanguages,
moduleName: 'attachment' // TODO: fix.
}
};
constructor() {
super('UserForm', 'user');
}
}
Solution: Mozilla react-jsonschema-form
A React component for building Web forms from JSON Schema.
Mozilla react-jsonschema-form
interface Todo {
id: String;
name: String;
completed: Boolean;
color: Color;
}
{
$schema: 'http://json-schema.org/draft-06/schema#',
properties: {
Todo: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
completed: { type: 'boolean' },
color: { $ref: '#/definitions/Color' },
},
required: ['id', 'name']
}
}
}
What is JSON Schema?
- "A Media Type for Describing JSON Documents"
Mozilla react-jsonschema-form
Mozilla react-jsonschema-form
- Provide a JSON Schema describing the data
- Provide a JSON Schema describing the UI
- Provide initial data
Form React component
Mozilla react-jsonschema-form
react-jsonschema-form | |
---|---|
Productivity | ⭐⭐ |
Performance | ⭐⭐⭐⭐ |
Flexibility | ⭐⭐ |
Components Reusability |
⭐⭐ |
Validation Isomorphism |
⭐⭐ |
Architecture
react-jsonschema-form | |
---|---|
Manage state | ✅ |
Data validation | ✍️ |
Theming | ✅ |
Error messages | ✅ |
Fields conf. | ✍️ |
Developer Experience
<ApolloForm> 👩🚀 👨🚀
import * as React from 'react';
import gql from 'graphql-tag';
import { configure } from 'react-apollo-form';
import { client } from './apollo';
import { applicationFormTheme } from './core/forms/themes/application';
const jsonSchema = require('./core/apollo-form-json-schema.json');
export const ApplicationForm = configure<ApolloFormMutationNames>({
// tslint:disable-next-line:no-any
client: client as any,
jsonSchema,
theme: applicationFormTheme
});
<ApplicationForm
config={{
mutation: {
name: 'create_todo',
document: gql`mutation {...}`
}
}}
data={{}}
/>
<ApolloForm> 👩🚀 👨🚀
GraphQL mutations
(fields + types)
JSON Schema
(validations)
+
<ApolloForm>
<ApolloForm> flexible API
- Schema options
- Theming options
- Error messages
- Callbacks
- Render props
import * as React from 'react';
import gql from 'graphql-tag';
import { configure } from 'react-apollo-form';
import { client } from './apollo';
import { applicationFormTheme } from './core/forms/themes/application';
const jsonSchema = require('./core/apollo-form-json-schema.json');
export const ApplicationForm = configure<ApolloFormMutationNames>({
// tslint:disable-next-line:no-any
client: client as any,
jsonSchema,
theme: applicationFormTheme
});
<ApplicationForm
config={
/* schema options */
}
data={{
/* data */
}}
ui={
/* UI options */
}
/>
<ApolloForm> flexible API
Schema options
- "Mutation mode"
- "Manual mode"
<ApplicationForm
config={{
mutation: {
name: 'create_todo',
document: gql`mutation {...}`
}
}}
data={{}}
/>
<ApplicationForm
title={'Todo Form'}
config={{
name: 'todo',
schema: schema({
todo: {
name: types.type('string', { required: true }),
completed: types.type('boolean')
}
}),
saveData: data => {
console.log('save !', data);
}
}}
data={{}}
ui={{}}
/>
<ApplicationForm
config={{
mutation: {
name: 'create_todo',
document: gql`mutation {...}`
},
ignoreFields: ['user.image'],
updateFields: {
'user.phone_number': { pattern: '(^[0-9\+]{5,}$)|(^[a-zA-Z0-9]{0}$)' }
},
requiredFields: ['user.email']
}}
data={{}}
/>
<ApolloForm> flexible API
Render props
export interface ApolloRenderProps {
// renderers
header: () => React.ReactNode; // render the form header (title by default)
form: () => React.ReactNode; // render the inputs
buttons: () => React.ReactNode; // render save and cancel buttons
saveButton: () => React.ReactNode; // render save button
cancelButton: () => React.ReactNode; // render cancel button
// actions
cancel: () => void; // trigger a cancel
save: (args: any) => void; // trigger a save
// state
isDirty: boolean;
isSaved: boolean;
hasError: boolean;
data: any;
}
<ApplicationForm
/* props options */
>
{
form => (
<div
style={{
backgroundColor: '#FFF',
padding: '20px'
}}>
<h2>
My form!
</h2>
<br />
{form.form()}
{form.saveButton()}
</div>
)
}
</ApplicationForm>
<ApolloForm> flexible API
Error messages
By default, <ApolloForm> do not display errors.
To enable it, you should provide some options to ui prop.
type ApolloFormUi = {
// default: false, should we display errors list at the top ?
showErrorsList?: boolean;
// default: true, should we display errors at field level ?
showErrorsInline?: boolean;
// you can provide a custom component to display error list
errorListComponent?: ErrorListComponent;
};
<ApolloForm> flexible API
Theming
- Templates render Fields and Widgets
- Renderers are <ApolloForm> specific:
- <saveButton>
- <cancelButton>
- <header>
interface ApolloFormConfigureTheme {
templates?: ApolloFormTheme['templates'];
widgets?: ApolloFormTheme['widgets'];
fields?: ApolloFormTheme['fields'];
renderers?: Partial<ApolloFormTheme['renderers']>;
}
<ApolloForm> internals
GraphQL Schema
JSON-Schema
schema
TypeScript
Mutations enum
Build
tools
At
runtime
JSON & TS files
imported at runtime
introspection
Files
genenations
configure a FormFactory against
a schema
building a mutation
form using the
FormFactory
<ApolloForm> internals : Build tools
GraphQL Schema
JSON-Schema
schema
TypeScript
Mutations enum
introspection
- schema.json
- mutations.d.ts
- apollo-form-json-schema.json
react-apollo-form fetch-mutations <graphqlEndpoint> <outpurDir>
<ApolloForm> internals : Build tools
What is an introspection query?
<ApolloForm> internals : Build tools
graphql-2-json-schema package
type Todo {
id: String!
name: String!
completed: Boolean
color: Color
}
input TodoInputType {
name: String!
completed: Boolean
color: Color
}
type Mutation {
update_todo(id: String!, todo: TodoInputType!): Todo
create_todo(todo: TodoInputType!): Todo
}
{
$schema: 'http://json-schema.org/draft-06/schema#',
properties: {
Mutation: {
type: 'object',
properties: {
update_todo: {
type: 'object',
properties: {
arguments: {
type: 'object',
properties: {
id: { type: 'string' },
todo: { $ref: '#/definitions/TodoInputType' }
},
required: ['id', 'todo']
},
return: {
$ref: '#/definitions/Todo'
}
},
required: []
},
// ...
}
},
},
definitions: {
'Todo': {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
completed: { type: 'boolean' },
color: { $ref: '#/definitions/Color' },
},
required: ['id', 'name']
},
'TodoInputType': {
type: 'object',
properties: {
name: { type: 'string' },
completed: { type: 'boolean' },
color: { $ref: '#/definitions/Color' },
},
required: ['name']
}
}
}
<ApolloForm> internals : at runtime
JSON & TS files
imported at runtime
configure a FormFactory against
a schema
building a mutation
form using the
FormFactory
<ApolloForm> internals : at runtime
- import definitions files
- configure a Form Builder
- Instantiate a Form
import * as React from 'react';
import gql from 'graphql-tag';
import { configure } from 'react-apollo-form';
import { client } from './apollo';
import { applicationFormTheme } from './core/forms/themes/application';
const jsonSchema = require('./core/apollo-form-json-schema.json');
export const ApplicationForm = configure<ApolloFormMutationNames>({
// tslint:disable-next-line:no-any
client: client as any,
jsonSchema,
theme: applicationFormTheme
});
<ApplicationForm
config={{
mutation: {
name: 'create_todo',
document: gql`mutation {...}`
}
}}
data={{}}
/>
<ApolloForm> internals : at runtime
<ApolloForm> internals : at runtime
Provide mutation name + options
Your application
<ApolloForm>
react-jsonschema-form
get json-schema for mutation +
transform UI props
handle rendering +
validations
<ApolloForm> benchmark
-
react-jsonschema-form provides:
- Extendable, with the fields and widgets system.
-
State management and validation,
handled with local state and local validation using ajv.
-
<ApolloForm> bring the last 2 key features:
- API/Client Interoperability: generate the form JSON Schema from a mutation definition
- Simplicity: provide some helpers to avoid the complexity of JSON Schema and enhance the extensibility of react-jsonschema-form.
Conclusion
Vue.js forms | redux-form | Formik | react-jsonschema-form | <ApolloForm> | |
---|---|---|---|---|---|
Manage state | ✍️ | ✅ | ✅ | ✅ | ✅ |
Data validation | ✍️ | ✍️ | ✅ | ✅ | ✅ |
Theming | ✍️ | ✍️ | ✍️ | ✅ | ✅ |
Error messages | ✍️ | ✍️ | ✍️ | ✅ | ✅ |
Fields conf. | ✍️ | ✍️ | ✍️ | ✍️ | ✅ |
Developer Experience
Conclusion
Vue.js forms | redux-form | Formik | react-jsonschema-form | <ApolloForm> | |
---|---|---|---|---|---|
Productivity | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
Performance | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Flexibility | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
Components Reusability |
⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
Validation Isomorphism |
nodejs-only | nodejs-only | nodejs-only | ⭐ | ⭐⭐⭐⭐ |
Architecture
Thanks for listening!
Still in beta, looking for collaborators ➡️ react-apollo-form
Let's build the future of forms with GraphQL
By Charly Poly
Let's build the future of forms with GraphQL
- 2,245