Form Components in a React Design System
Presentational Component Package
In order to build a set of presentational components that are reusable across applications, you may consider creating a dedicated presentational component package (PCP). In React, the increasing popularity of CSS-in-JS (e.g. styled-components, Glamorous) makes this a viable architecture choice that allows you to include interface components as a dependency without reliance on a specific build system to handle CSS files.
In addition to creating a portable component library, this approach enforces a clear separation between presentational and container components.
Application state and architecture choices
Where possible, and in order to improve portability and separation of concerns, a PCP should abstracted away from the architectural choices of the peer application, including state management.
For example, you may expect peer applications will use Redux or a Redux-like architecture, but if a peer application wanted to replace Redux with something else in the future, would you want the PCP to limit this choice? It would be better if the peer application developer was free to make this choice without refactoring the PCP.
I would therefore avoid introducing Redux as a dependency into a PCP.
The issue with form components
Presentational components can be built so that they are independent of application state by following the principles of lifting state up.
Managing a large form that lifts every component up to application state and then back down to the components again can be a bit daunting. A popular way to manage forms in React is redux-form. This provides a recognised pattern for managing form state.
Should form components in a PCP use components or patterns from redux-form? If you were to introduce redux-form as a dependency, this in turn introduces a dependency on redux, so this should be avoided.
In addition, peer applications may want to move from redux-form to something else in the future, such as final-form or formik, so creating components that are independent of these libraries would be preferred.
How can we build presentational components that are compatible with redux-form and performant, but:
- ensure separation between the use of redux and the presentational form components
- without introducing redux-form as a dependency
- allow peer applications to choose not to use redux-form or redux.
Proposed approach
First lets break down the required components in to a few categories:
- Forms — complete forms, e.g. a form that contains a name, address and submit button.
- Fieldsets — several fields that work together, but don’t nescessarily represent a full form, e.g. an address.
- Fields — Maintaining a single value, with a single onChange event for lifting state up. e.g. the first line of an address plus a validation message.
Treat your complete forms as you would containers. The presentational component library should provide the tools necessary (fields, wrappers and sometimes fieldsets) for a container to build the form it needs, but not actual forms. If you want to build containers that don’t contain DOM or CSS, ensure the component library provides wrappers for form spacing etc — containing presentational logic but no reference to form elements or state.
If using redux-form, the container would utilise Redux Form’s Field component. In order for the component library to be compatible with this, field components would need to follow the redux-form’s spec for incoming props.
The props provided by
redux-form
are divided intoinput
andmeta
objects
https://redux-form.com/7.1.2/docs/api/field.md/#props
This ties your component library’s fields to redux form’s prop spec, but not to redux-form itself. This doesn’t mean that a peer application has to use redux-form — just that they need to pass properties following the same property specification. If the peer application already has its own pattern/property specification, a higher order component could be created to map between the two.
Types of form components
Forms
- Manages: a complete form
- Lives in: peer application
- Follows: redux-form HOC
Apply similar patterns as you would to redux containers — e.g. avoid HTML and styles.
FormSection (fieldset type 1)
- Manages: a fieldset
- Lives in: peer application
- Follows: redux-form FormSection
Apply similar patterns as you would to redux containers — e.g. avoid HTML and styles.
Fields (fieldset type 2)
- Manages: a fieldset
- Lives in: PCP
- Follows: redux-form Fields spec
Note this is less performant than using a FormSection.
Field
- Manages: a single input plus meta data such as warnings and errors.
- Lives in: PCP
- Follows: the redux-form Field prop spec
- Props: input, meta
Maps redux-form property structure to an input and processes meta data.
Field with object value (fieldset type 3)
- Manages: multiple inputs plus meta data such as warnings and errors.
- Lives in: PCP
- Follows: the redux-form Field prop spec
- Props: input, meta
Similar to Field, but has an object as a value so that several inputs can be used together to produce a single (object) value.
Input
- Lives in: PCP
- Follows: Standard React Input patterns — see Lifting State Up.
- Props: checked, name, onBlur, onChange, onDragStart, onDrop, onFocus, value
See https://redux-form.com/7.1.2/docs/faq/customcomponent.md/
FormWrapper
- Lives in: PCP
For providing the tools to space out or group form components.
How does this all come together?
I have a project in progress in GitHub that illustrates the patterns described above. It currently has temperature (as used in React’s documentation) and address input components.
The temperature component follows the field with object value and input patterns above. The address follows the formsection, field and input patterns.
Form validation
By putting form validation, event handlers and other application logic in the in the peer application rather than the PCP, it makes the PCP components more reusable/flexible. I’d suggest that your PCP should only contain interface/presentation specific code and that form validations don’t fall in to that category.
Summary
- Don’t introduce a dependency on redux or redux-form in your presentational component library.
- You can follow redux-form’s specification for prop types without introducing a dependency on redux-form itself.
- A Presentational Component Package should contain the UI tools for building forms (inputs, validation messages, wrapping components for layout), but building up a series of fields in to a form should be done in your containers.
- If a peer application doesn’t want to use redux-form, they don’t have to. They just need to pass properties in the same format.