Layering in new behavior with React

I’ve talked in the past how I like the approach React leads me to when it comes to building apps. How does such grandiose talk play out when it’s time to add a new, unexpected feature? Let’s check it out. I’ve been building an installation app for Spinnaker, and one of our top notch developer advocates gave it a spin.

Results? Not good. Too many presumptions were built into the UI meaning he had no clue where to go. Message to me? Fix the flow so it’s obvious what must be done and what’s optional.

So I started coding in a feature to flag certain fields REQUIRED and not allow the user to reach the installation screen without filling them out. Sounds easy enough in concept. But how do you do that?

With React, what we’re describing is an enhancement to the state model. Essentially, keep filling out fields, but earmark certain fields as required, and adjust the layout of things to show that, while barring other aspects of the interface in the event those same fields aren’t populated.

So I started with a little bit of code to gather a list of these so-called required fields, and it looked like this:

If you have done any React programming, assigning something via this.state[…] = foo should set off bells in your head. You always, always, ALWAYS used this.setState(…). So what’s up?

Rules are rules until they aren’t. This is a situation that defies the concept. I don’t WANT to set the state such that it triggers a ripple through the DOM. Instead, this code happens right after the initial state model is initialized. And I’m setting it using values populated in the previous line, because you can’t initialize required, pointing at this.state.api in the same call the initializes this.state.api itself!

With this list of required fields setup, we can start marking up the fields on the UI to alert the user. I have a handful of React components that encapsulate different HTML inputs. One of them is dedicate to plain old text inputs. Using the newly minted required list, I can adjust the rendering like this:

Notice the little clause where it checks this.props.settings.requires.includes(this.props.name)? That is a JavaScript ternary operation that if true, returns the label with extra, highlighted text. Otherwise, just render the same label as always.

By applying this same tactic to the other React components I have for rendering each selection on the UI, I don’t have to go to each component and slap on some new property. Instead, the designation for what’s required and what’s not it kept up top in the state model, making it easier to maintain and reason about.

At the top of the screen is a tab the user clicks on to actually install things and track their progress. To ensure no one clicks on that until all required fields are populated, I updated that tab like this:

A little function that detects whether or not all required fields have been filled out is checked, and if so, renders the HTML LI with its onClick property filled out with the handler. If NOT, then it renders the same component, but NO SUCH onClick property is present, meaning it just won’t respond.

This is the nature of React. Instead of dynamically adjusting the DOM model, you declare variant layouts based on the state of the model. This keeps pushing you to put all such changes up into the model, and writing ancillary functions to check the state. In this case, let’s peek at requiredFieldsFilledOut:

This tiny function checks this.state.required, counts how many are “truthy”, and if the count matches the size of this.state.required itself, we’re good to go.

In case you didn’t know it, React encourages you to keep moving state-based functions up, closer to the state model itself. It’s super simple to pass along a handle to the function to lower level components, so they can still be invoked at lower levels. But anytime one component is trying to invoke another one in a separate part of the hierarchy, that’s a React-smell hinting that the functions should be higher up, where they can meet. And that results can trickle down to lower level components.

I encountered such a function stuffed down below in a lower level component. The need to trigger it sooner based on a change in the state had me move it up there. Now, when the Installation tab is clicked, that function is run, instead of waiting for the user to click some button down below.

Suffice it to say, I rehabbed the UI quite nicely. The thing is, with React it’s not hard to effect such change in a couple days, compared to the days or weeks of effort combined with testing it might have taken with classic manipulated-the-DOM apps, where you have to hunt down gobs of wired event handlers and find every little nuanced operation you coded to make things operate correctly.