Based on a true story

Once upon a time, not too long ago, I was a new developer. Fresh out of my bootcamp, I was in love with React and Node and wanted to write code that was flashy and new inside. Code that leaned on hooks to build cool UI things.

When I was asked to implement an onboarding feature for new users, I jumped in with glee. We wanted to do a questionnaire. We were working on a single page application, and to avoid overwhelming the users with questions too quickly we decided a transition between questions was in order.

“Wow!,” I thought.

“A set of components that all need to collect information?”

“I should control all these from one place, and then bundle up that data!”

“A single component can manage all the state.”

“And then submit it to the backend!”

Like every new bootcamp grad, I said: “I know what this needs! A custom hook!”

So I implemented it. The state got unwieldy. I didn’t abstract things properly. When we wanted to change the order of the questions, well … you can imagine.

Then we needed to validate emails. Some was backend validation, sure, but mostly we wanted to make sure the right gist of an email was there before we submit.

Of course, it was a bad user experience to navigate back through all 20 pages in the questionnaire to edit that email. So we made a cool new component to summarize everything. While we’re here, let’s allow for edits in this summary—in case we made a mistake along the way! Naturally, we can show the validation errors here, too.

Do you see where I’m going?

const TotallyNotAForm = () => {
    const {
        email,
        setEmail,
        phoneNumber,
        setPhoneNumber,
        insuranceChoice,
        setInsuranceChoice,
        submitTotallyNotAForm
    } = useTotallyNotAFormDataHook()
    // Add a  WHOLE LOT MORE to this list, and you get the point

    const [currentQuestion, setCurrentQuestion] = useState(1)

    const nextQuestion = () => {
        setCurrentQuestion(currentQuestion + 1)
    }

    return (
        <>
            {currentQuestion === 1 &&
                <Question text="What's your email?" 
                          response={setEmail} value={email} 
                          nextQuestion={nextQuestion}/>
            }
            {currentQuestion === 2 &&
                <Question text="What's your phone number?" 
                          response={setPhoneNumber} value={phoneNumber}
                          nextQuestion={nextQuestion}/>
            }
            {currentQuestion === 3 &&
                <Question text="Please Select Your Type of Insurance" 
                          response={setInsuranceChoice} 
                          value={insuranceChoice} 
                          nextQuestion={nextQuestion}/>
            }

            { /* 20 some other question components later */ }

            {currentQuestion === 24 && 
                <SummarizeAndEditAnswers submit={submitTotallyNotAForm}
                /*
                you better believe every single thing from the
                hook got passed in here too! 
                * */
                />}
        </>
    )
}

In my mind this was way cooler than some stinky old form. This was modern. I did a cool abstraction of the idea of a question, so that code is totally reusable, man.

Please don’t ask questions about how I decided what type of input field to render.

Also please don’t ask about how the validations worked. There was a bunch of props on the <Question /> component that had to do with an ’error’ state, and just … bleh.

Pretty please don’t ask about how the hook had to mangle data to make it conform to what our API expected. Or how it manipulated the API response to indicate validation errors. Or how I chose to style the errors.

In retrospect, I did a huge disservice to my teammates, and to our client. I introduced a lot of tech debt that required refactoring. We had to decouple the Question component, introduce a Router, and lean on contexts to pass things around more elegantly. I misused the cool tools from the start, but the end result required an unreasonable amount of tools to wire up and make work properly.

The “summary view” should have been feature one.

I should have just built a form.

What’s in a form?

Let’s be honest. There’s a whole lot of work on the Internet that’s about collecting and representing user input data. Digital paperwork makes up a significant portion of what websites have to do. Better, more intuitive paperwork? Hopefully, yes! But paperwork all the same.

Why on earth are we contorting ourselves into all sort of strange shapes? Why are we creating beautiful single page experiences out of what is essentially just cruft enabling us to do the actually cool stuff?

Naturally, I did make mistakes being that fresh into my career. The blame does not lie solely at the feet of React here. I misused the tool. I lacked insight into what was valuable to our users. I had not come across any wisdom about how to do the fundamentally boring, necessary evil, form building in a sleek way. I wanted to use React in a way that made me feel clever.

I think, therein, lies the rub. Contrast my horribly misguided form with tools offered elsewhere.

Many tiny things make a big thing

The most basic app every full stack web framework tutorial starts with is building a blog. A blog with posts and comments. Those tutorials interact with the Comment and Post model directly. They generate some HTML, handle validations, and often bundle the Create and Edit views naturally. We get a lot of the behind the scenes details for free.

Standalone JavaScript front ends have to package up a heck of a lot of context. We denormalize from our backend understanding of our domain to drive behaviour on the front end. Even the most basic of forms in React has to understand, or be given tools to:

  • render input fields
  • control input field values
  • package the data to be sent
  • send data to the API
  • request state from the backend, for our edit view
  • shove the data into the edit view appropriately
  • handle success responses from the backend
  • handle failure responses from the backend
  • handle validation errors from the backend
  • implement its own front-end validations
  • manage the current state of the form being filled out
  • share code between create / edit views

If we want to implement multiple views for a single form, you then also have to consider a whole host of other challenges:

  • state management across component levels
  • passing state around
  • appropriate component abstractions
  • controlling the form inputs
  • giving the form the ability to submit itself

What a delicate dance of things we must consider. No wonder Early-Career-Daniel bungled it.

It’s not a React problem

I don’t mean to harp on React here. It doesn’t matter what front end library we’re using, we have to attend to these matters. The proliferation of packages that are all about building better form components, or managing our form data, or validating our forms, or even simply how to do a specific type of user input, each in their chosen component library tells me just how big this problem is. Angular has Reactive Forms, and Vue has a whole bunch of these too.

We are innately giving up a whole heck of a lot of things, and introducing a lot of complexity by making super slick form UIs with standalone JavaScript component libraries.

I think we should be asking some questions of ourselves here.

  • Is this a reasonable tradeoff?
  • Do we really want to inflict all this pain on ourselves? On our early-career developers?
  • How much time in our cycles are we wasting on the gritty cruft that is the reality of digital paperwork?
  • Do we need to reach for a standalone front end to make those digital documents?
  • Is this approach mandatory to effectively create an experience for users to share their details with us?

I’ve seen some really challenging React forms crumble under the self-inflicted complexity of managing all those steps in the delicate dance.

How many of our forms have fallen into disrepair, and make our users grit their teeth and suffer because they’re just broken?

The model had new validations enforced, and now the front end can’t edit any of the old entries because those validations fail.

The form didn’t validate, and the component didn’t handle the response properly so it re-rendered and we lost all the user input.

Are we winning the user experience game, or are the forms defeating us?

S.O.S.(F).

Save our Submittable Forms.

Ultimately, I’m not you. I’m not on your project.

Maybe all your forms are tiny. One or two entries that are easy to keep in state, easy to format into a post request, and easy to validate.

Or, maybe your forms are like mine. Growing in complexity, adding new features, taking new shapes, bloating, struggling to swim under the weight of model changes, denormalized domain details, controlling state, and data formatting and,

and,

and.

I’m sending out an S.O.S.(F).

PS: I hope you’ll come with me. I have an idea to share with you.

Daniel Huss

Person An icon of a human figure Status
Double Agent
Hash An icon of a hash sign Code Name
Agent 00140
Location An icon of a map marker Location
Calgary, AB