Charly POLY - Senior Software Engineer at
What about SPA, JavaScript ecosystem forms?
Forms handle the experience that user have with data
Developer Experience
User Experience
In short, fast and smooth experience
Vue.js forms
redux-form
Formik
What it doesn’t solve:
What it solves:
What it doesn’t solve:
What it solves:
The best way to manage your form state in Redux.
What it doesn’t solve:
What it solves:
Build forms in React, without the tears 😭
Vue.js forms | redux-form | Formik | |
---|---|---|---|
Manage state | ✍️ | ✅ | ✅ |
Data validation | ✍️ | ✍️ / ✅ | ✅ |
Theming | ✍️ | ✍️ | ✍️ |
Error messages | ✍️ | ✍️ | ✍️ |
Fields conf. | ✍️ | ✍️ | ✍️ |
Developer Experience
Vue.js forms | redux-form | Formik | |
---|---|---|---|
Productivity | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Performance | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
Flexibility | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
Components Reusability |
⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Validation Isomorphism |
nodejs-only | nodejs-only | nodejs-only |
Architecture
Since forms are about data, why not build typed forms?
The JavaScript eco-system new era:
- TypeScript: typed JavaScript
- GraphQL: typed data exchange
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');
}
}
A React component for building Web forms from JSON Schema.
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?
Form React component
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
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={{}}
/>
GraphQL mutations
(fields + types)
JSON Schema
(validations)
+
<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={
/* schema options */
}
data={{
/* data */
}}
ui={
/* UI options */
}
/>
Schema options
<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={{}}
/>
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>
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;
};
Theming
interface ApolloFormConfigureTheme {
templates?: ApolloFormTheme['templates'];
widgets?: ApolloFormTheme['widgets'];
fields?: ApolloFormTheme['fields'];
renderers?: Partial<ApolloFormTheme['renderers']>;
}
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
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>
What is an introspection query?
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']
}
}
}
JSON & TS files
imported at runtime
configure a FormFactory against
a schema
building a mutation
form using the
FormFactory
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={{}}
/>
Provide mutation name + options
Your application
<ApolloForm>
react-jsonschema-form
get json-schema for mutation +
transform UI props
handle rendering +
validations
react-jsonschema-form provides:
<ApolloForm> bring the last 2 key features:
Vue.js forms | redux-form | Formik | react-jsonschema-form | <ApolloForm> | |
---|---|---|---|---|---|
Manage state | ✍️ | ✅ | ✅ | ✅ | ✅ |
Data validation | ✍️ | ✍️ | ✅ | ✅ | ✅ |
Theming | ✍️ | ✍️ | ✍️ | ✅ | ✅ |
Error messages | ✍️ | ✍️ | ✍️ | ✅ | ✅ |
Fields conf. | ✍️ | ✍️ | ✍️ | ✍️ | ✅ |
Developer Experience
Vue.js forms | redux-form | Formik | react-jsonschema-form | <ApolloForm> | |
---|---|---|---|---|---|
Productivity | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
Performance | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Flexibility | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
Components Reusability |
⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
Validation Isomorphism |
nodejs-only | nodejs-only | nodejs-only | ⭐ | ⭐⭐⭐⭐ |
Architecture
Still in beta, looking for collaborators ➡️ react-apollo-form