periqles
Painless forms for GraphQL.
Demo β
Table of Contents
Getting Started
To add a <PeriqlesForm />
to your Apollo or Relay client, follow these steps.
Prerequisites
- React (v. 16.8.0 and up)
npm install react
Installation
- Install periqles from the terminal.
npm install periqles
- Import PeriqlesForm into your frontend.
// MyReactComponent.jsx import PeriqlesForm from 'periqles';
Server
Periqles relies on introspection queries to intuit the optimal form UI from your project's GraphQL schema. These queries will hit your server in the form of POST requests to /graphql
. To use periqles, you must expose your schema at that /graphql
endpoint.
In our demo, we use the client-agnostic express-graphql
package to spin up a server in Node for our GraphQL API. See the documentation here and our code here. Apollo projects may use the Apollo Server without problems.
//server.js
const express = require('express');
const {graphqlHTTP} = require('express-graphql');
const app = express();
const {schema} = require('./data/schema/index.js');
app.post(
'/graphql',
graphqlHTTP({
schema: schema,
pretty: true, // pretty-print JSON responses
}),
);
If you are not using the /graphql
endpoint to serve your API, options include configuring your server to redirect requests to /graphql
to the correct endpoint or using a build tool like Webpack to proxy requests to /graphql
to the correct address.
Schema
Currently, the introspection query used by periqles expects to find named input types on the schema. I.e., if you tell a <PeriqlesForm />
to generate a UI for your AddUser
mutation, it will query your schema for a type called AddUserInput
. Then it will render an input element for each input field listed on the AddUserInput
type.
This means that periqles can successfully introspect this mutation:
#schema.graphql
type Mutation {
addUser(input: AddUserInput!): AddUserPayload
}
# The mutation input is named and defined separately from the mutation.
input AddUserInput {
username: String!
password: String!
email: String!
gender: GenderEnum!
pizzaTopping: PizzaToppingEnum!
age: Int!
}
... but trying to introspect this mutation will cause your GraphQL server to throw back a 400 Bad Request
error:
#schema.graphql
# The mutation input is not named and is defined in-line.
type Mutation {
addUser(input: {
username: String!
password: String!
email: String!
gender: GenderEnum!
pizzaTopping: PizzaToppingEnum!
age: Int!
}!): AddUserPayload
}
This is a high-priority area of improvement for us. We welcome PRs and other contributions.
Usage
<PeriqlesForm />
takes a number of props, including optional props to override its default logic for more fine-grained control over the apperance and composition of the form, the data sent to the API on submit, and state-management behavior.
PeriqlesForm Props
These are the props available to all clients. See below for more usage information specific to your client.
-
mutationName
: string (required) β The name of a mutation as it appears on your GraphQL schema, e.g. 'AddUser' or 'AddUserMutation'.- If this is the only prop provided, periqles will render a form with default HTML intuited based on the name and scalar data type of each input field. E.g., an input field of type 'String' will result in
<input type="text">
. If the name of the input field appears in periqles' dictionary of common input fields, it will render a more specifically appropriate element. For example, a string-type field with the name 'password' will result in<input type="password">
, and a field named 'mobile' will result in<input type="tel">
.
- If this is the only prop provided, periqles will render a form with default HTML intuited based on the name and scalar data type of each input field. E.g., an input field of type 'String' will result in
-
specifications
: object (optional) β If you wish to control the HTML or state management of a particular field, provide the instructions here. Periqles will fall back to its default logic for any fields or details not specified here.header
: string (optional) β If you wish to put a header on your form, e.g. "Sign up!", pass it here.fields
: object (optional) β Each key onfields
should correspond to the name of an input field exactly as it appears on the schema. (E.g., based on the schema example above, 'pizzaTopping' is a valid key to use.) You can override defaults for as many or as few fields as you wish.element
: string (optional) β The HTML element you wish to use for this field, e.g. 'textarea', 'radio', 'datetime', etc.label
: string or element (optional) β The text, HTML, or JSX you wish to appear as a label for this field.options
: array (optional) β Whether or not this field is listed as an enumerated type on the schema, you may constrain valid user input on the frontend by using 'select' or 'radio' for theelement
field and providing a list of options here.option
: object (optional) β Specifies an option for this dropdown or group of radio buttons.label
: string or element (required) β The label you wish to appear for this option.value
: string or number (required) β The value to be submitted to the API.
render
: function(params: {formState, setFormState, handleChange}) (optional) β If you wish to completely circumvent periqles' logic for rendering input fields, you may provide your own functional component here. The component you specify will completely replace the field<PeriqlesForm />
would have otherwise rendered. Parameters:formState
: object (optional) β The name and current value of each input field as key-value pairs.setFormState
: function(newFormState) (optional) β A React setState hook. Overwrites the entirety of formState with whatever is passed in.handleChange
: function(Event) (optional) β Destructures the input field's name and value off event.target to pass them as arguments to setFormState.
src
: string (optional) β Whenelement
is 'img', the URL to use for thesrc
attribute.min
: number (optional) β Whenelement
is 'range,' the number to use for themin
attribute.max
: number (optional) β Whenelement
is 'range,' the number to use for themax
attribute.
-
args
: object (optional) β If there are any variables that you want to submit as input for the mutation but don't want to render as elements on the form, pass them here as key-value pairs. Example use cases include client-side authentication information or theclientMutationId
in Relay. E.g.:const args = {userId: '001', clientMutationId: ${mutationId++}}
. Fields listed here will be excluded from the rendered form but included on the mutation when the form is submitted. -
callbacks
: object (optional) β Developer-defined functions to be invoked when the form is submitted.onSuccess
: function(response) (optional) β Invoked if the mutation is successfully submitted to the API. In our demo (Relay, Apollo), we use onSuccess to trigger a very simple re-fetch and re-render of a component which displays<PeriqlesForm />
's output.onFailure
: function(error) (optional) β Invoked if the mutation fails to fire or the API sends back an error message. Use this to display meaningful error messages to the user.
Validation
Currently, periqles is able to validate input fields listed as non-null and of type string
on the GraphQL schema. It will prevent the user from submitting the form if required text fields are left blank, including enumerated fields (represented by dropdowns or radio buttons) that are of type string
.
This is high-priority area of improvement for us. If you have specific needs around validation, please open an issue or submit a PR.
Relay
In addition to the optional and required props listed above, <PeriqlesForm />
requires the following props when used in a Relay client:
environment
: RelayEnvironment (required) β Your client's RelayEnvironment instance; necessary to send a mutation.mutationGQL
: GraphQLTaggedNode (required) β Your mutation, formatted as a tagged template literal using thegraphql
tag imported fromreact-relay
. (NOT the version provided bygraphql-tag
.)
<PeriqlesForm />
uses the commitMutation function imported from Relay to fire off mutations when the form is submitted. If you pass an onSuccess and/or an onFailure callback on the callbacks
prop, they will be invoked by commitMutation's onCompleted and onError callbacks, respectively.
CommitMutation takes additional callback parameters that are not currently included on <PeriqlesForm />
's callbacks
prop, namely updater
, optimisticResponse
, optimisticUpdater
, configs
, and cacheConfigs
. We plan to support these callbacks soon. If this is a high priority for your use case, please let us know by opening an issue, or submit a PR.
Here is a basic example of how to use <PeriqlesForm />
in Relay:
// MyComponent.jsx
import React, {useState} from 'react';
import {graphql} from 'react-relay';
import PeriqlesForm from 'periqles';
const ADD_USER = graphql`
mutation UserProfile_AddUserMutation($input: AddUserInput!) {
addUser(input: $input) {
username
password
email
gender
pizzaTopping
age
}
}`;
const MyComponent = ({relay}) => {
return (<div>
<h1>Sign Up</h1>
<PeriqlesForm
mutationName={'AddUser'}
mutationGQL={ADD_USER}
environment={relay.environment}
/>
</div>);
};
Apollo
In addition to the optional and required props listed above, <PeriqlesForm />
requires one additional prop when used in an Apollo client:
useMutation
: function (required) β Your custom mutation hook, built using the useMutation hook imported from@apollo/client
.
Here is a basic example of how to use <PeriqlesForm />
in Apollo:
// MyComponent.jsx
import React from 'react';
import { gql, useMutation } from '@apollo/client';
import PeriqlesForm from 'periqles';
const Signup = () => {
const ADD_USER = gql`
mutation AddUser($input: AddUserInput!){
addUser(input: $input){
username
password
email
gender
pizzaTopping
age
}
}`;
const [addUser, response] = useMutation(ADD_USER);
return (<div>
<h1>Sign Up</h1>
<PeriqlesForm
mutationName={'AddUser'}
useMutation={addUser}
/>
</div>);
};
Styles
Periqles comes with its own basic stylesheet, but it also attaches class names to each of its HTML elements that you can target in CSS for additional styling. We've tried to keep our default styles in that sweet spot between "enough to be presentable" and "adaptable to any design scheme." If you think we should provide more or less CSS, give us a shout in the issues.
Each element has two class names which follow this format:
- "periqles-[element type]": e.g., 'periqles-textarea', 'periqles-radio-option'
- "[field name]-[element type]": e.g., 'biography-textarea', 'gender-radio-option'
Contributing
If you would like to contribute to periqles, please fork this repo. Commit your changes to a well-named feature branch then open a pull request. We appreciate your contributions to this open-source project!
License
Distributed under the MIT License. See LICENSE
for more information.
Maintainers
Built with:
- React (Hooks)
- GraphQL
- Relay
- Apollo
- the support of OSLabs