Using React’s state to manage data entry

Adam Rackis
3 min readMay 28, 2016

--

Redux is an outstanding tool for managing global state in a React application. Storing your application state in a central location, normalized, and shaped as needed with selectors creates a uniquely clear, simple, and testable architecture. However, managing data / form entry on any non-trivial-sized ui doesn’t quite fit as well. There’s a Redux-form utility which attempts to bridge this divide, but it wasn’t readily clear to me exactly how it worked, and native React state solves this simply enough. How it does so is the topic of this post.

Use case

A book / library management application would support both editing a book’s information — title, publisher, authors, etc — while also letting the user enter new ones. These two use cases — creating new, and editing existing books — would post to separate end points, but still (hopefully) reuse the same UI.

Ignoring for the moment how bland and boring this UI currently is, the rest of this post describes how I implemented this using state. I saw no easy, convenient way to do so with Redux, but React’s native state and event handling features made it a breeze.

Coding it up

Loading the book to edit

Determining when a new book to edit has been passed into the component is easy.

componentWillReceiveProps(nextProps){
if (this.props.bookToEdit !== nextProps.bookToEdit){
let bookToStart = { ...nextProps.bookToEdit };
this.revertTo = bookToStart;
this.revert();
}
}
revert(){
this.setState({
bookEditing: this.revertTo,
titleMissing: false,
authorsChanged: false,
pendingSmallImage: '',
smallCoverUploadError: ''
});
}

We’re saving a snapshot of the object that was passed in. This is useful to let the user revert the whole form, and cancel his pending changes. From here the only real work to do is wiring up the inputs to sync the bookEditing object in our state with the user’s changes.

Something like this would work

<input
onChange={evt => this.setState({ bookEditing: { …this.state.bookEditing, titlee: evt.target.value } })}
value={this.state.bookEditing.title}
className=form-control
placeholder=Title (required)” />

But it’s quite verbose and unpleasant.

Fuctional programming ftw

A higher order function can make things a bit nicer

<input
onChange={this.syncStateFromInput(‘title’)}
value={bookEditing.title}
className=form-control
placeholder=Title (required)” />

With this now added to our constructor

this.syncStateFromInput = name => evt => 
this.setState({ bookEditing:
{ …this.state.bookEditing, [name]: evt.target.value } });

(I’m doing my best to format things for Medium)

Can we do better? Sure. The field in question is repeated twice for each input. Why not abstract it? React’s functional components make this especially nice.

Adding this to our constructor

this.SyncedInput = props => <input 
onChange={this.syncStateFromInput(props.syncName)}
value={this.state.bookEditing[props.syncName]}
{ ...props } />

and this to our render method

let SyncedInput = this.SyncedInput;

lets us now just do this

<SyncedInput 
syncName="title"
className="form-control"
placeholder="Title (required)" />

Integrating with Redux

Are we cut off from Redux now? Absolutely not. The component takes the book to edit, the save method, etc as props, and is utterly unaware of what those props point to; they could be items out of our Redux state, or class methods and state from a component sitting above it.

Here’s what it looks like being used with Redux

<ManualBookEntry
title={editingBook ? `Edit ${editingBook.title}` : ''}
dragTitle={dragTitle}
bookToEdit={editingBook}
isOpen={this.props.bookEdit.isEditing}
isSaving={this.props.bookEdit.editingBookSaving}
isSaved={this.props.bookEdit.editingBookSaved}
saveBook={book => this.props.saveEditingBook(book)}
saveMessage={'Saved'}
onClosing={this.props.stopEditingBook} />

And here’s what it looks like used with “vanilla React”, from a component’s state.

<ManualBookEntry
title={'Manually enter a book'}
dragTitle={'Click or drag to upload a cover image. The uploaded image will be scaled down as needed'}
bookToEdit={this.state.manualBook}
isOpen={this.state.inManualEntry}
isSaving={this.state.isSavingManual}
isSaved={this.state.manualSaved}
saveBook={book => this.saveNewBook(book)}
saveMessage={'Book saved. You can enter another, or close'}
startOver={() => this.manuallyEnterBook()}
onClosing={() => this.manualEntryEnding()} />

Here’s what the entire code looks like

https://github.com/arackaf/booklist/blob/master/react-redux/applicationRoot/rootComponents/manualBookEntry-es6.js

Issues

Obviously the helpers I wrote aren’t terribly re-usable. But as the code above shows, React lets us make these pieces fairly easily. For me, data input is a relatively infrequent requirement, so making ad hoc components like this serves my purposes nicely. If this were a more frequent need, I would likely check out Redux-form, linked above.

It’s certainly not beautiful needing to create the SyncedInput in the component constructor, but given that fat arrows create a lexically bound `this` value, the alternative would be to use a regular function declaration in place of the fat arrow. Use whatever feels better to you.

Am missing something? Leave a comment, or ping me on Twitter.

--

--