A few weeks ago, I was asked to build a simple JavaScript application that works both standalone and embedded in a web page via an iframe. The project is a dynamic form that saves and loads form field data as JSON, for the purpose of scheduling slides in KioSign.
Besides the requirement to save and load JSON, I was also asked to implement the form in React, as well as fulfill some other requirements (not all of which have been met yet). This was quite the learning curve, considering I’d never written a script that used AJAX or dynamically-generated React Components.
The Basic Form
A basic form in React is pretty easy, as long as you’re familiar with HTML (which any web designer should be) and somewhat familiar with objects in JavaScript.
See this sample from the React documentation:
<form onSubmit={this.handleSubmit}>
<label htmlFor='txtField'>Name:</label>
<input name='txtField 'type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
As you can see, it’s very similar to HTML or XML, as with most JSX elements. The key differences are the value, onChange and onSubmit attributes. The latter two are hooked to component methods that track changes in field values and process those values on submission. In particular, changes are stored in the component’s state as the input field is changed, and that value is then retrieved by the value attribute.
Because React handles component rendering, this has the effect that user input is tracked in real time, after which the data can be submitted to another function, method or component in the program. This behavior is also what I’ll take advantage of in the next post to contextually display form fields.
A Data-Driven Form with Formik
So, getting a static form with a few fields in React is easy, and that’s great for prototyping, but before I even think about saving and loading data, I want to make sure that the data will be processed to create the desired form.
The requirement of the project is to use JSON, which is great because that’s native JavaScript syntax. Having some familiarity with JSON, I know that the form data will look something like this:
[
{
content: 'Google Slide',
length: 10,
},
{
content: 'WordPress',
length: 5,
},
]
Obviously, the real data is far more complex – it has to track the content URL, slide length, start and end times, start and end dates, days of the week, and two additional check boxes: priority and disable. But for now, that’s enough to prototype a dynamic form that reads JSON, creates the proper number of repeaters and fills each repeater with the right data.
As I’m thinking it through, I read the documentation to see how different form fields are implemented: I need text fields, but also selects and check boxes. After reading the entire page on controlled form fields, I see a sign from Mark Zuckerberg himself:
If you’re looking for a complete solution including validation, keeping track of the visited fields, and handling form submission, Formik is one of the popular choices. However, it is built on the same principles of controlled components and managing state — so don’t neglect to learn them.
Probably Not Mark Zuckerberg Himself
Oh, that sounds convenient. Let’s do that!
Formik’s documentation is…not great. Even the developer himself was found admitting that it needs updated, and I think he said that 2 years ago. Regardless, I spend a few hours digging in and come out familiar enough with Formik to implement. But I still need a dynamic form.
Long story short, I thought about it, Googled, and it turns out Formik has a component (FieldArray) for just this situation. So after a lot of trial and error, I get Formik to render each object in my small JSON as its own set of identical fields that each track their own value, and after even more trial and error, I’m able to submit the entire form’s values using the format I want.
But that trial and error was nothing compared to learning AJAX.
A Jaunt Across XMLHttpRequest
So I don’t do a lot of fetching data across the web. Last time I tried was using React and it didn’t go so well. Part of that I blame on W3Schools not providing reference on the Fetch API. I thought XMLHttpRequest was still the hip way of doing web requests, and boy am I glad I was wrong.
What I wasn’t wrong about is that JavaScript sucks at file management. This is for security reasons, to stop strange scripts from harvesting all your personal data (unless you’re a tech company in America). But it makes “legitimate” file management nearly impossible.
So I looked at solutions. I’m using create-react-app to setup my build environment, so I know that Node.js is running my code on a virtual server. And because Node.js is for facilitating JavaScript on the back-end, I figure Node.js can manage files on the back-end, like PHP. I love PHP, but I’d rather not code my own file management system in it and worry about how to send and receive data using Fetch. So a native solution is ideal!
Except the Node.js filesystem doesn’t work with React, I guess? Well ok, someone must have written a library to do filesystems in React. Sure enough, someone did! Oh, it requires flow?
Here’s the thing about create-react-app: it’s great for a beginner like me, and a lot of libraries work flawless with it. But some don’t, and usually because they require precompilation that CRA isn’t equipped for. I’ve only been using React for about a month, so I don’t know how to fix a lot of these issues yet.
I tried another library, but this one didn’t work because it required an experimental Babel syntax. I figure, Babel is already installed, surely I can setup a single plugin? Okay, I follow a tutorial to use Babel plugins with CRA without ejecting, great! And then spent 2 days trying to get that to work, before finally admitting that I have to do the thing I didn’t want to do: write a filesystem handler in PHP.
As it turns out, with the Fetch API, it’s really easy! My code is really short on both ends, and with PHP doing it I can easily host the scheduling JSON on a separate server from the React app and add in authentication code, etc. if I need to later. So all around, a great solution, and knowing how the Fetch API works will be a huge help in the future.
Expanding the Form
So now I’ve got a form that submits field values to a PHP script to save it as JSON, and vice versa. The user can create as many repeaters as desired, so the next step is to bring prototype more features: it needs all the fields, some way to change the order of repeaters, and basic validation.
Getting the rest of the fields in was pretty easy, using examples in the Formik documentation, though one major hurdle was that Fetching the JSON wasn’t working right. Hard-coding the JSON worked fine, and the JSON seemed to be stored in State on component mount, but it took me a few days of bewildered troubleshooting to find the culprit: Formik is picky about retrieving initial values from State more than once in the component lifecycle. In the end, it was one line of code: enableReinitialze
on the Formik component. After that, Formik worked like a charm.
Re-ordering the repeaters took awhile to figure out. I saw in the documentation the function I needed to swap positions in the FieldArray array, but no example of how to invoke it. Again, it took some trial and error and Googling, but I figured it out after realizing that the function I wanted was on a method that my code already used for removing repeaters. Seeing that, I was able to create queue buttons in no time, though there’s still some unexpected behavior that merits further investigation after the specs on the app are nailed down.
Validation was an interesting challenge. At this point, I was still very new to Formik and not sure what made it “tick.” I need a way to conditionally display repeaters based on if their publish date was no longer valid, without compromising the integrity of the data. The final solution was pretty elegant, using a short function, the moment library, and good old ternary operations. Repeaters are now filtered in real-time based on the validity of the publish and unpublish dates.
Next Up
That’s the point where I uploaded my build of the prototype to my server to show the client, which can be seen here.
The next blog post will cover further filtering options and repeater validation, an embedded content preview, and contextual field displays.