Handling and validating user input in a React app

Edward Byne

Software Engineer

GitHub/Twitter: ejbyne

Introduction

  • Forms are everywhere on the web
  • They look easy, but handling input and validation can be complex:
    • Where do you store input data?
    • How do you handle server errors?
    • Do you need frontend validation?
    • How do you provide the user with a seamless experience?
    • When do you validate?
    • How do you display error messages?
  • There's no one way to get it right, but the backend API schema and frontend architecture should be considered at an early stage

Storing data

  • Where do you store the user input?
  • 3 main options:
    • Redux store
    • Local component state
    • Find an npm library which manages the data for you e.g. redux-form
  • My preference is local state
this.setState({ localState: 😍 })

My approach to state

  • Redux store for server data and global state
  • Local state for isolated data
  • If only the component needs to know about the data, why share it with the whole application?
    • U.I. state e.g. is the dropdown open
    • Form data - is only temporary, will be thrown away
  • Each application is different, so it's not one size fits all
  • I personally found redux-form too restrictive and it introduces a Redux dependency in your UI components
  • Why do people hate setState?

It's not just me

Using local component state is fine. As a developer, it is your job to determine what kinds of state make up your application, and where each piece of state should live. Find a balance that works for you, and go with it.

Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.

Sometimes you'll want to move from Redux state to React state (when storing something in Redux gets awkward) or the other way around (when more components need to have access to some state that used to be local).

The rule of thumb is: do whatever is less awkward.

Basic form

Example

  • Local state
  • material-ui form components
  • It's easy if the user gets it right!

Frontend validation

The wrong way

  • Instinctive approach of many developers is to start by handling validation directly in the UI component
  • Either in the component itself or an imported utility function
  • Examples:
    • Name cannot be longer than 20 characters
    • Date must be in the future
  • But this is business logic - we should keep this logic separate from our UI components

Frontend validation

A better approach

  • Let the UI components focus purely on UI logic
  • Let the Redux layer handle any business logic
  • Pass in validation callbacks as props, which are called by event handlers
  • Pass in validation errors as props, which are then simply displayed

But...

  • It brings additional complexity/development time
  • Our backend will have its own validation layer, which will send back validation error messages
  • Why not start there first and test the UX experience?
  • We need to be able to parse backend errors anyway and can always add frontend validation later
  • Some constraints can't be validated in the frontend e.g. unique username

Do we actually need frontend validation?

Backend validation

Example

  • Node.js server
  • Hapi framework
  • Built-in validation using Hapi's joi library

Parsing server errors

Backend

Async action creator

UI Form

Post request

Error

Action creator

Error

Submit callback

Reducer

Error action

Store

Updated state (with error)

Selector

Parsed error

Raw error

User friendly messages

Example

 

Maybe that's enough?

Or maybe the backend can offer pre-submission validation endpoints? 

  • e.g. /api/reviews?validate=true
  • When validate query present then validate without processing further

Or maybe not?

  • May not be suitable for large forms e.g. wizards with multiple steps, where user needs regular feedback
  • May not be suitable for mobile apps, where user is offline or has slow network connection

So, if we do need frontend validation...

2 options:

  1. Replicate the backend validation rules in frontend code
  2. Or, preferably, fetch the validation rules from the backend and use them to generate JavaScript validators

Backend API schemas

  • Schema - metadata which tells us how request and response payloads are structured
  • JSON Schema is the most well-known standard specification for defining JSON payloads
  • If we agree on a schema spec with our backend team, we can write a JS function to read this schema and generate a validator
  • There are existing JS validator libraries for JSON Schema
  • But how can we generate these schemas?

Swagger

and the Open API Specification

  • Specification for defining RESTful APIs
  • YAML or JSON format
  • Uses a subset of JSON Schema to define request and response payloads
  • 2 approaches:
    • Design driven - produce code based on OpenAPI document
    • Use libraries to automatically generate OpenAPI document based on code
  • Lots of Swagger libraries available for generating code based on OpenAPI document and vice versa
  • e.g. SpringFox, hapi-swagger

Frontend validation

Example

  • I used Enjoi to make it easy for me (creates a Joi validator out of a JSON Schema)
  • Other popular JSON Schema validator libraries e.g. AJV
  • Validator called on input blur and prior to submission
  • Error messages passed back to UI component via the same "errors" prop
  • Validation error only shown for field if it has been clicked/tabbed into

Further considerations

  • Keep the form logic generic - it can then be made reusable using the HOC or render prop patterns
  • Internationalisation techniques to provide user friendly messages
  • Use the backend schema data to set constraints on input fields and prevent validation errors e.g.
    • extract enum values into a dropdown list
    • set maxlength on text input

Conclusion

  • Don't be afraid to use component state
  • Frontend validation is not easy
  • Don't assume it's always necessary
  • Start with backend validation
  • Error messages format should be defined in API documentation at an early stage
  • If you need frontend validation, consider ways of sharing validation logic between frontend and backend
  • Keep business logic separate from UI logic

Edward Byne

Software Engineer

GitHub/Twitter: ejbyne

Thank you

Copy of Handling and validating user input in a React app

By Edward Byne

Copy of Handling and validating user input in a React app

  • 226