Potemkin Life

Forms Done Right

**Note: A different version of this post was published by the popular Medium Publication The Startup

Forms are a Common Pattern, So Get Them Right

Basically every web/mobile application has a form in it somewhere. Lot's of web applications are nothing but one big form. There is a high probability that if you are a web/mobile developer you have had to implement the pattern. Without investing time and effort upfront to handle the form/error validation state gracefully, implementing forms can be full of boilerplate that ends up mishandling data and triggering server errors. How can we fix this problem? Luckily, the ubiquity of the form pattern means the modern ecosystem of open source projects and modules have made available tools that allow handling and building forms with grace and ease.

The company I work for recently outgrew our Django Admin app. The Django Admin interface is solid and it had been working for our operations as their internal tooling team for quite some time, but as volume grew we needed something more flexible, scalable, and powerful. Our team is quite small and has to move fast, so it is important that the technology choice would prioritize developer ergonomics and speed.

We evaluated a few different options and decided to extend the Django REST API we had in place to include a GraphQL schema. We used Graphene/Graphene-Django to easily expose all of our current resources as a graph. On the front-end we decided to use React, Apollo, and Formik for managing state, and Material-UI as the component library.

Using this stack we managed to abstract away most of the terrible boilerplate and hair-pulling-out error handling into a more beautiful, simple, and declarative solution.

Let's walk through building a simple forms app with the aforementioned stack to understand how leveraging new technologies can make form development incredibly simple.

Note: all embedded code throughout this post will come from a working project that you can fork and play with yourself. You can find that repo here.

Django and Graphene

First we have to model our application data. I'll do that by defining a few Django models that describe the fields and relationships of our data. Profile, Business, Reference, and ProfileReview.

After we have modeled our application data with Django, we need to describe that data model in the GraphQL context and create a GraphQL server that knows how to fetch our data. Graphene-Django provides abstractions that make it easy to add GraphQL functionality to our Django project.

The DjangoObjectType from the graphene-django library makes this extremely easy. As you can see from the code above, all we have to do is define a new class that inherits from DjangoObjectType and then let the class know which model it should define its fields from. The library then creates the GraphQL type for you. The field definitions on the Query class at the bottom of the file expose query-able fields through the root node of our GraphQL schema. graphene-django has also supplied default resolvers for these fields, so we don’t even have to write the data resolver for a field if we do not need any custom resolving functionality.

With the simple code we've written we already have data that can be resolved through queries being sent to our GraphQL endpoint.

Reading is nice, but writing is nicer.

Mutations and Django-Graphene's Model Form Mutations

Django ModelForms are great. They provide a neat interface for validating and saving data that maps closely to one of your models. You can safely skip the next few paragraphs if you are already familiar with them, but if not let me show you how great they are with an example. Here’s a definition of a Profile form mapping to the Profile model that we defined earlier.

Super easy. The fields attribute on the Meta class definition allows us to set which fields we would like to be a part of the form validation. Let me show a printout from a session in the Django shell to exhibit how this works.

Let me go over in a bit more detail what is happening here. The form takes two keyword arguments, data, and instance. If it does not receive an instance kwarg, then it tries to create an instance of the model rather than update a current instance. It then makes sure that all the required data to create that piece of data has been passed to it. If it is not, then the form will update it’s error attribute to let you know which non-blankable fields were missing. Next, we try updating the data (notice that we have an instance kwarg this time). Someone has tried passing in a string as the age. The forms interface lets us know that we screwed up again, by letting us know the field and the reason the update failed. This is a very powerful feature that we will use later.

Now that we have modeled our data, created ways to read and write that data, it’s time to build our client! But wait, first I’d like to take a little detail to show off some cool features that using a GraphQL schema over a traditional REST API has bought us.

GraphQL Introspection: Enforcing Types Across the Client-Server Interface

GraphQL schemas support a very cool feature called introspection. Introspection allows you to enforce types across the client-server interface. This is powerful because it can reduce the need for end-to-end/integration testing. But how does this magic work, you may be asking? Let me show you using another awesome tool that GraphQL provides us: GraphiQL.

GraphiQL is a super useful tool that allows you to explore your schema in a small application that runs in your browser. It also supplies an editor where you can enter and test run queries and mutations. This is great because you can read/write to your DB without writing any client code and without the need for a tool like cURL, HTTPie, or Postman. Furthermore, the query logic you write and edit in GraphiQL translates directly to the query logic you will implement in your client code. allows you to test out queries and mutations by supplying an editor to and test writing queries/mutations. Here are a few examples of what the exploration sidebar of the application looks like. Here is me checking out all the different fields defined on the root node of the query schema:

Example of different defined queries

We can tell exactly what arguments a query field expects and what type that query will return. Next, we can click through the types to find out exactly how each one is defined, as follows:

Our Schema's definition of Profile type as found in GraphiQL

Cool! That corresponds nicely to our Profile model that we defined in our Django app. Now let’s look at our mutation.

updateProfile Mutation in GraphiQL

Here we can see that this mutation must take in an input of type UpdateProfileInput. As we did with the Profile type we can click through to the UpdateProfileInput to find out its type definition as well:

UpdateProfileInput type

This is even cooler! With the exception of clientMutationId, ignore that for now, this corresponds directly with the ModelForm we defined. GraphiQL has “introspected” the fields and types of those fields of the input we need to supply when we want to update the profile form. It even knows which fields are mandatory and which are not (as denoted by !). When I try to make an invalid update, the GraphiQL tool will let me know:

Invalid Input for the updateProfile mutation

When we write the client code we will use this same power of introspection to make sure that our client does not make invalid attempts to update our data.

Now that we have our back-end built out and read/write-able we are ready to start building some beautiful UI. Before we start building, let me go over some of the libraries I am going to use on the client-side to make this as easy as pie.

React

I'm sure many of you are familiar with React, so I'll make this short. React is a very popular JS library that let's one build declarative UI components.

Material-UI

Material-UI is a library built on top of React that supplies ready-to-use components to build your application with. All the components have consistent and intuitive APIs that make them easy to work with. Their documentation supplies interactive visual examples along with the code used to build the example, so working with Material-UI is essentially a drag-and-drop experience. Material-UI has a powerful way of supplying your own theme to all the different application components, so it is simple to apply your brand’s palette to the application. Because it is so popular you may not want to use it for any sort of flagship customer-facing app, but it is a great option for building out internal tools or less important sections (forms) of your app.

Apollo Client

The Apollo Client “is a complete state management library for JavaScript apps”. It works particularly well with React because it takes advantage of the React Hooks pattern to fetch and mutate data. With the Apollo client you do not have to write any data plumbing boilerplate. It gives you a beautiful, declarative, and obvious way to fetch data and update your components. As you will see once we write some client code, all we have to do is specify the data we would like from our GraphQL API via a GraphQL query, and Apollo Client handles the requesting and caching of that data, as well as the updating of our UI (with loading and error states as well as fetched data).

Quick Aside on Declarative Code

You may have noticed how much I have been touting the gains of declarative-ness through the libraries I am using. A quick anecdote about how awesome it is to have your codebase be comprised of mostly declarative code: I recently put up a large PR (including server-side and client-side code), building out one of the first components of our new app with this stack, and the reviewer’s comment was, “Wow, to review that PR I just read through it top to bottom once, didn’t run the code, and I understood exactly how it worked, all of the data requirements of each component, and what it was trying to achieve.” Merging 1000+ loc PRs that range the full stack with sub 20-minute code reviews is a huge win for most developer teams. After the initial framework setup, writing the code felt as logically simple and easy as reviewing it does. So, this tech stack is for more than just allowing for easy form building. If you follow best practices with these different libraries, all your code will be incredibly clear and easy to both read and write.

Formik

Formik is a JS library billed as a way to “build forms in React, without the tears 😭”. I would agree with that sentiment. All you have to do is wrap your input/form fields with the functional React component they supply, give it a few props to handle the specifics of your form, and Formik deals with all the state management, error handling, validation boilerplate that one always dreads having to write.

Typescript

Finally, I’ll be writing all the front-end code in a superset of JavaScript called TypeScript. Using TypeScript will allow us to leverage the type-safe client-server interface we get from using a GraphQL API.

All right, now that we’ve gone over the different technologies we will be using, let’s get our hands dirty and take a look at what developing with these technologies will look like!

Parent-Child Structure using Apollo Client

GraphQL allows for declarative and specific data-fetching logic. This has some pretty cool downstream implications that allow us to organize our code in intuitive ways. The first big gain that this gets us is that each child component is in complete control of its own data needs, not just at the resource level but all the way down to the scalar field. Even cooler, each child component declares exactly what data it needs. Let’s look at an example to get a better idea of what this looks and feels like in practice.

Here is the beginning of a parent component I might write for our simple Forms app:

Before we get into the meat of things, let’s go over a neat feature of the useQuery hook: it automatically updates the object it returns indicating the status of the request. If the query successfully returns, we get the results in the data key. If it errors, we get the details and any messages about why in the error key (this will be very useful later). If it is still in flight, it lets us know with the loading key. This abstracts away a lot of annoying data fetching/state handling boilerplate into this single line.

Okay, now let’s look at the actual query we are supplying that hook. We define it with the gql tag above. The gql tag is a module that comes with the Apollo Client library. When you tag a template literal with this tag, the GraphQL query that is defined in that template literal will be parsed into a standard GraphQL AST (Abstract Syntax Tree). This is the most common (and the only way we will use) to supply queries/mutations to the GraphQL client through the useQuery and userMutation hooks.

Within the tag, you should notice a couple of things. First off, we have specified this query’s entry point into our GraphQL schema by using the profile query we defined in the schema with Graphene. Looking at the way we have defined this query, we know we must either a primary key or an id to get the specific profile we are looking for. As you can probably tell from the query definition, we use the primary key to look up the profile.

The next thing you probably notice is the spread operator and the embedded expression ${ProfileForm.fragments.data}. This is a fragment. It is used for composing queries. That description sells short the true power behind this simple concept. Having a simple and concise way to compose large and complex pieces of query logic from many smaller and more specific pieces of query logic allows for something quite cool: we can make the parent component responsible for the data fetching (with the useQuery hook), while any child components are responsible for declaring their own data needs. Let’s try to understand that better by looking at the actual fragment we are supplying to the ProfileFormsQuery.

When we write a fragment we give it a name and define the schema type the fragment is selecting fields from. This fragment selects data from the profile type. When we spread it into it’s parent query, it must be in the selection field of the profile type. This fragment should live with the functional component which data needs it defines, and using Apollo best practices should be added as fragments attribute to that functional component.

Parent components typically have more than one child component. When a child component that depends on server-side state is added, it should always come with a fragment that declares its own data needs. It should even declare all query logic that it shares with its sibling components. This allows us to open a component file and understand immediately what that component’s data requirements are. Furthermore, gql is smart enough to ensure that when it composes the final query out of all the (possibly redundant) fragments, it will not repeat any query logic. With this structure, you get to have your cake and eat it too. Your children components declare their own data needs, while only one component (the parent component) is responsible for sending the one request that will fetch all that data.

Auto-Generated Prop Interfaces with TypeScript and Apollo's Codegen

When using TypeScript with Apollo, fragments yield another great feature. As part of the suite of developer tools Apollo that supplies, we can use Apollo Codegen to generate the shape of the data objects we expect to receive from the server. (Apollo codegen also generates Swift code, Flow annotations, and Scala code). The tool parses all the gql tags that you have defined in your code and generates type annotations for each one by running an introspection against your GraphQL API. The type that gets generated for your children’s fragments will be the interface for the data props that your parent component passes down to the child. Let’s take a look at the type generated from the fragment ProfileData that is responsible for populating our ProfileForms component:

So now this automatically generated interface called ProfileData can be used to type the props that get passed into our ProfileForms component ensuring that the types enforced at the client-server interface are persisted all the way to the UI/state interface.

Now let’s see how we can use the Formik library to make building out that UI/state interface as easy as possible.

Managing Transient Form State and Form Submits with Formik

Formik helps manage all the transient form state before and after submitting the form. The library supplies a functional component that wraps the set of input fields that make up the form. Its own interface (the props it takes in) is a set of initial values, a submit handler, and a validation schema. Let’s look at how to build out the form that handles our Profile information to get a better idea of how Formik will make our lives super easy.

The fields of the profile props, the input to the updateProfile mutation, and the fields we define to handle the user’s input should all align perfectly so that the spread operator can be used to succinctly pass these values to each other. (For terseness the above gist only shows the “name” field, but imagine the “age” and “phoneNumber” fields are also defined.)

Let me be more explicit about the direction of the flow of data here: props.profile is being supplied by the query that uses our fragment above, so it should have name, age, phoneNumber fields → We spread that into the initialValues prop on the Formik component → Formik then supplies any values it gets passed to it in this prop to the functional component that has a name that matches that key → So we should have a component that matches each of the (name, age, phoneNumber) fields that are part of props.profile → Now we can spread the field object that formik supplies us into the props of the TextField component. Doing this gets rid of all the boilerplate of defining the value and the input handler of the TextField. Finally, we can also spread the values object (the state coming from our Fields) right into the input of the updateProfile mutation. Because quite a bit of magic is happening under the hood here, it may take a few passes of the Formik documentation to understand how it all works, but once grokked it is ridiculously trivial to add more fields to this form. All that has to be done is to add another Field component with the name of a field that exists on the props.profile object and the rest does itself.

Schema Validation with Yup

The one thing I didn’t mention above is the validationSchema prop on the Formik component. This is what handles all the validation and error state setting. Formik leverages another super cool module called Yup to allow a super declarative schema definition. Here is the validationSchema that I defined for the name, age, and phoneNumber fields.

With Yup, you can define your schema shape and the requirements for validation in what feels like plain English. Looking at the above schema, you can quickly tell that the name is required and is a string, the phoneNumber is also required, and it must match a particular RegExp, and that the age is not required but must be a number. Super cool. The last thing to note, is that Formik will set the meta.error field on the Field props to the message that you supply this schema. So if the phoneNumber doesn’t match the phoneRegExp, the meta.error field on phoneNumber field will be “Phone is not valid”. If the name field is completely empty, the meta.error field on the name field will be “Required”.

Demo of Form

All that functionality with so little code. If you want to play with the code or the demo application itself yourself clone this repo github.com/bicknest/forms. To build and run the frontend code just run yarn start.

Handling Server Errors

One of the quirks of GraphQL is error handling. A GraphQL API will return 200s in many surprising situations. The classic GraphQL trope about this goes something like this: Server Error? 200, response OK. Validation Error? 200, response OK. Server explodes? 200, response OK.

One of the advantages of this is that you can treat errors and error responses as data that the user queried for. Certain error states are expected and with a little bit of pre-meditation can be handled like a piece of data that the user queried for. One example: imagine an application that requires an OAuth token for a Google API to make some requests. If the user tries to take any action that requires this authentication but the token has either expired or never existed, our server might respond with an informative message and a URL they can click through to take the user through the authentication flow. There are two pieces to the response, a message and a url. By defining an optional field type on the mutation payload called error, GraphQL lets us shape our error responses in any way we choose. Furthermore, it gives us type safety on these error responses.

Conclusion

On the first application I worked in my dev career we treated every form that got added to the app as a one-off with its own boilerplate form validation, error handling, and state setting. This resulted in function components bloated with useless boiler plate. Furthermore, we had no guarantee that input types from the UI would be type-safe with the backend modeling of the data. This resulted in server validation errors when each of our adhoc form solutions was not rigorously tested. Don’t develop like this…take the time to vet and invest in the right technologies to make form development a breeze.

- 2 likes