Enabling the back button to cancel confirmation dialogs in React apps

Users expect that dialogs, action sheets, alerts, drawers, and other pop-ups will close when the back button is pressed. This is especially true on Android (where the back button is globally available in all apps), but this behavior makes sense for any apps running in a web browser too — on both mobile and desktop. Usually, when a dialog is open, the back button should act the same as if the user had chosen the “Cancel” option. The worst thing that could happen (but it’s surprisingly common, unfortunately) is that the dialog will close and the browser will navigate away from the page that opened the dialog!

In a React app, we can use the browser’s history (exposed by our routing library of choice) to ensure that pressing the back button when a dialog is open closes the dialog and keeps the app on the same screen that opened the dialog. As a bonus, if the user navigates forward again, we can restore the dialog to its previous state very easily. Let’s take a look.

For convenience, I created an example on CodeSandbox to demonstrate the final result. In this example, I’ve added Reach Router and Material UI as dependencies to provide the routing/navigation and some UI controls. The concept that I’m demonstrating here should work just as well with React Router and other UI control libraries without too many changes.


Edit React dialogs with back button

In our example app, we have a list of users. When you click/tap a user’s name, a dialog will pop up with some actions that can be performed. Or you can press cancel to close the dialog. Our goal is to ensure that the browser’s back button will close the dialog without navigating away from the list of all users — the same as if the cancel option were chosen with a click/tap.

We can pass the user to dialog using the browser history’s state object. We’ll push a new state onto the stack (without changing the URL), and the dialog will check the current state to know when it should open or close.

Let’s start by rendering the list of users using some components from Material UI:

// inside render()
<List>
  {users.map(user => {
    return (
      <ListItem
        key={user.id}
        button
        onClick={() => this.selectUser(user)}
      >
        <ListItemIcon>
          <Person />
        </ListItemIcon>
        <ListItemText primary={user.name} />
      </ListItem>
    );
  })}
</List>

We have an array of users that we map to ListItem components. When a list item is clicked/tapped, we call a function called selectUser() that will manipulate the browser’s history.

For reference, a user in the array looks like this:

{ id: 1, name: "Franklin Nelson" }

In selectUser(), we use the navigate() function provided to our component by Reach Router to add the user to the history stack.

selectUser(user) {
  let newState = { selectedUser: user };
  this.props.navigate(this.props.location.pathname, { state: newState });
};

Notice that we use this.props.location.pathname (also provided by Reach Router) to ensure that the URL does not change. We’re staying at the same location, but a new history entry is added that has a different state. This means that when we go back, we’ll stay on the same page.

Next, let’s see how our UserActionsDialog component is rendered.

As a first step, we should check if the current history state has a selected user. If no user is selected, we’ll simply return null because we don’t want to render the dialog:

// inside UserActionsDialog's render()
let user = undefined;
let state = this.props.location.state;
if (state && "selectedUser" in state) {
  user = state.selectedUser;
}
if(!user)
{
  return null;
}

If a user is selected, we should render our dialog. It might look something like this:

// continued from above
return (
  <Dialog open>
    <DialogTitle>{user.name}</DialogTitle>
    <List>
      <ListItem
        button
        onClick={() => alert("Option 1 selected for " + user.name)}>
        <ListItemText primary="Option 1" />
      </ListItem>
      <ListItem
        button
        onClick={() => alert("Option 2 selected for " + user.name)}>
        <ListItemText primary="Option 2" />
      </ListItem>
      <ListItem
        button
        onClick={this.cancelItem_onClick}>
        <ListItemText primary="Cancel" />
      </ListItem>
    </List>
  </Dialog>
);

Notice that we have access to the full user object selected in the parent component. We’re accessing its name field here.

For simplicity, the first two actions simply call alert(). The third option is named “Cancel” and it should close the dialog. Clicking this item calls cancelItem_onClick(), which appears below:

cancelItem_onClick = () => {
  window.history.back();
};

When we call window.history.back(), the selected user is removed from the history stack and our UserActionsDialog component will re-render and return null. That will cause the dialog to be hidden.

Notice that we don’t do anything to cancel except to navigate back. With that in mind, we don’t need to do to do anything more to enable the back button. It just works! In fact, after we navigate back, the browser’s forward button will work too, and the dialog will re-open… including the same selected user.

Back in the parent component that contains our List of users, let’s see how to add our UserActionsDialog:

// this goes after the list of all users
<Router>
  <UserActionsDialog path={this.props.location.pathname} />
</Router>

Notice that we place UserActionsDialog inside a Router component. This ensures that the location prop is passed into the dialog so that we can find the selected user. You can nest routers, so it’s fine if there’s another router further up the component tree.

As I mentioned before, the route’s path is the same as its parent. We shouldn’t need to change the URL when selecting a user because that wouldn’t make much sense if the user wanted to share the page that contains the dialog. Since the current state object stores the user, that’s what we use to determine if the dialog should be open or closed.

Even when using some kind of router, many web apps don’t account for all cases where the back button should behave a certain way. Opening dialogs to select an action is a common place where the user might press the back button to cancel, and you don’t want your app to navigate to a different page unexpectedly. On Android, this is especially important because the global back button is such a core part of the user experience. However, it’s just as useful in a web browser on any platform, including on desktop.

Migrating an Apache Flex app to React: Milestone 2

Back in January, I wrote a post about reaching the first milestone in the process of converting my app Logic.ly from Flash Player to plugin-free HTML & JavaScript. That first milestone involved converting a subset of the app (a non-editable view of a simulated circuit) so that I could embed interactive examples inside the help files for Logic.ly. Once I got that finished, it was time to move on to the next, more ambitious, milestone. For the last ten months (roughly two hours a day, four to five days per week), I’ve been working on enabling nearly every feature of the full app. Last week, I replaced the online demo of Logic.ly that was previously using Adobe Flash Player with a fully JavaScript and HTML DOM powered version, built with React and TypeScript.

I thought I’d share some of my thoughts on the libraries, tools, and platform that I’ve immersed myself in throughout 2018, and how it compares to my long experience with Adobe’s Flash platform.

React

I’ve come to enjoy React quite a lot. Especially React combined with TypeScript. React uses JSX, an XML-like way to describe UI that is very similar to MXML in Flex. However, JSX flips things around compared to MXML. Instead of script being embedded in XML, XML is embedded in the script. This makes it really easy to conditionally render parts of the view based on state, or to even loop through arrays to repeat multiple instances of the same type of component. As you can imagine, JSX’s flexibility has the potential to lead to some pretty gnarly spaghetti code. However, with care, JSX feels to me like a superior paradigm to MXML.

React’s ecosystem is very active and full of high quality, custom components and libraries. Reach Router, react-beautiful-dnd, and react-loadable are just three wonderful examples of open source packages that are worth checking out. The only thing that I’ve struggled with is finding the “perfect” UI control library that has a decent number of core components, works on both desktop and mobile, and also offers a robust styling system.

I’m using Semantic UI for the UI Logic.ly, which is generally pretty serviceable. However, like many React UI control libraries that I evaluated, there are a ton of components that I don’t need right now (which isn’t a bad thing), but some more basic components are surprisingly missing (which is kind of weird). For instance, Semantic UI doesn’t offer a slider/range component. In fact, the slider/range is one of the most commonly missing components in various other React UI control libraries that I also evaluated, which has me baffled because it’s such a common piece of UI. In the end, I used the regular HTML <input type="range"> as a fallback, but it feels out of place and requires some hacky styling.

Redux

As you may know, the most popular architecture framework for React is Redux, and that’s what I’m using for Logic.ly. It provides the same kind of structure/organization to a project as many of the MVC frameworks that Flex developers probably encountered over the years — like Robotlegs, Swiz, PureMVC, Cairngorm, and others. Interestingly, it also heavily leans into functional programming concepts, including expecting the data it stores to be immutable.

I’m not a huge fan of Redux, to be honest. It’s better than no framework at all for a big app like Logic.ly, but I hope that something better comes along eventually.  I understand Redux’s benefits, but from a developer experience perspective, I just don’t like the way it feels. Some people claim that it has a lot of boilerplate, but I didn’t feel like that was something that bothered me. Sure, there’s probably some room to streamline things a little, but I think I’m more turned off by reducer concept and immutability requirements. I understand that this type of architecture is a big part of functional programming, but it feels “off” in a UI development environment where things are often more naturally represented with an object-oriented paradigm.

Recently, I saw some people talking about Immer, a library that provides immutability with an API that acts more like mutable data. That’s really interesting to me, and I think that it might improve the Redux experience to be more like what I prefer. Hopefully, I can find some time in the future to check out Immer and give it a test drive.

TypeScript

After years with ActionScript, TypeScript offers many of the same familiar improvements over JavaScript. It also provides a ton of really useful new features that ActionScript doesn’t provide (and won’t be getting in the future). Here are some of my favorites:

More flexible interfaces. You don’t need to create a class to have an object implement an interface in TypeScript. If you have an IPoint interface that has x and y properties, you could set a variable’s type and assign a value like this:

let p: IPoint = { x: 10, y: 15 };

Type inference. A smarter compiler that can figure out a variable’s type from context is probably the biggest advantage of TypeScript over ActionScript, in my opinion. It saves an incredible amount of typing. In the code example below, the compiler is smart enough to figure out that n should be a number and s is a string:

let n = 123;
let s = "Hello";

Proper generics. In ActionScript, we only had the Vector type. In TypeScript, you get the full power of creating your own types that use generics. That means less casting required and better chances that the compiler will catch a mistake.

let map = new Map<string,MyType>();

Arrow functions. Being able to define a local function where this matches the scope of the surrounding method reduces so many headaches. Plus, the shorthand syntax of arrow functions is super slick.

myArray.forEach( item => console.log(item) );

The let keyword. Using var in JavaScript and ActionScript creates a variable in the scope of the local function. Other languages usually create variables in the scope of the nearest block. So, if you created a variable inside the body of an if() statement or a for() loop, you wouldn’t be able to access it outside that block. Using let makes JavaScript and TypeScript variables more like those other languages:

if(someCondition)
{
    let value = false;
)
value = true; //error because value is not in this scope

Performance

Logic.ly isn’t your typical form-based app, and it can potentially display hundreds (or thousands) of objects in the editing surface at the same time. To keep performance high, I needed to customize the update behavior for some components to ensure that React was performing well and not rendering too frequently. This isn’t anything that’s wrong with React. In fact, there are established best practices for my situation, which is a great indicator of maturity.

In some places, I could easily switch to using PureComponent, and here and there, I created some custom shouldComponentUpdate() methods for even more control over rendering. I made heavy use of the React Dev Tools to ensure that my components weren’t rendering too often, and ultimately, all of my work paid off. I haven’t even had the chance to try out the new React Profiler yet, which I expect will improve performance even more.

Many parts of Logic.ly running on top of React and JavaScript are much faster than the old Flex and Flash Player version of the same app. Part of that is probably thanks to the ground-up rewrite. Anytime you start from scratch, you get to drop some of the baggage that you had before, and you can make better architecture choices from the beginning. However, React’s virtual DOM and decreased reliance on events/observers/binding is probably a non-trivial part of it too.

There’s also the fact that JavaScript virtual machines have kept on improving over the last several years, while the ActionScript virtual machine hasn’t evolved since Adobe stopped investing heavily in the platform several years back. There was a time when types in ActionScript gave it a huge advantage. Now, JavaScript VMs do things like create their own internal shadow types to use with dynamic objects behind the scenes, which basically makes JavaScript as fast as ActionScript once interpreted code is converted to bytecode. Combine that with a ton of other JS optimizations, and while I’m sure that Flash Player is still faster here and there, it doesn’t have the major advantage it used to have 10 or more years ago.

One place where switching to JavaScript shines is being able to take advantage of code-splitting. This allows Logic.ly to load only the parts of the code that it actually needs, and load the rest on demand. I’m able to show UI to the user faster than before (so that they can start using the app sooner, instead of waiting for everything to complete loading), and I can ensure that their bandwidth usage is minimized. Flex had the concept of modules, and any SWF can load other SWFs, of course. Additionally, SWFs support preloading the first frame to show some kind of loading screen to keep the user distracted. However, setting up code splitting in JavaScript is significantly easier and more powerful, in my opinion. It happens automatically any time that I reference a module with import(). It’s great! If Flash Player were still a competitive platform for deploying to the web today, I’m sure that Flex would have a smarter automatic module system by now, but as a fading platform, it’s (not unexpectedly) falling behind on things like this.

Limitations of the web platform

Even with all of the great advancements for frontend web developers over the last several years, there are still places where Flash Player still does things better. Here are a couple of places where I ran into trouble or where things felt half-baked.

There’s no JS equivalent to localToGlobal() and globalToLocal() to convert points between different coordinate spaces. Well, that’s not completely true. SVG exposes an API for converting points between the SVG coordinate space and the global coordinate space of the surrounding page, and I guess that’s probably what I’m supposed to use. I found that it’s a little clunky, but I made it work… but only successfully in Chrome and Edge. I discovered that once you use CSS transforms on some elements, such as scaling or rotation, converting points between different coordinate spaces did not work correctly in both Firefox and Safari. This is absolutely necessary in Logic.ly, since the editing surface can be zoomed in and out, so I had to write my own conversion function to handle scaling when trying to position objects in two different coordinate spaces. It was not ideal. I’m actually surprised that this functionality is broken in two major browsers. I’ve made heavy use of localToGlobal() and globalToLocal() in Flash for well over a decade, and I know they’re commonly used in a ton of ActionScript projects, so I would have expected them to be nearly as important to JS developers.

I find it frustrating that there’s no global event dispatching API in JS that anyone can use with their own custom types and objects. The HTML DOM can dispatch events, and other types like XMLHttpRequest can too. This feels like an odd omission. Sure, there are third party libraries, but none of them are completely compatible, so you could end up with a dozen different implementations of events in one app, if you aren’t careful. The EventDispatcher type (and IEventDispatcher interface) in ActionScript powers the Flash display list, networking APIs, and basically everything that’s built-in, and it can also be used by custom ActionScript classes. This helps everything feel more cohesive in the Flash world, and it limits the amount of duplicated work required by developers in the ecosystem. At least the JS world seems to be trying to standardize on Promises for many things now where events were used in the past. However, adoption of promises has been in progress for years, and we’re not anywhere near using them for everything yet. I don’t see this situation improving any time soon, which is unfortunate.

On to the next milestone!

Whew! That was quite a lot for me to bring together. So, what’s next? Well, I still need to migrate the desktop version of Logic.ly to the new codebase. I will probably use Electron when I start working on that. However, I think that I will save that for a bit further in the future because I recently decided that it was time to start thinking about finally bringing Logic.ly to mobile.

Milestone 3 will be updating the Logic.ly online demo to provide a more mobile-friendly UI on Android tablets and iPad. Right now, mobile users get the desktop version with a few very minor tweaks. I knew that migrating to JS/HTML and adding in a mobile UI would be too much to handle all in one step, so I decided to keep it simple for the previous milestone. Finally, in the last couple of weeks, I’ve started working on the mobile UI, and things are moving forward smoothly and rapidly. Soon after I update the online demo for mobile, I’d like to expand the codebase into a full-fledged mobile app that I’ll release on the iOS App Store and Google Play. These new sources of revenue will hopefully enable me to have more time to work on updates to Logic.ly in the future! I plan to look into using Capacitor to build the mobile app.

Three ways to conditionally render components with React and JSX

Sometimes, you want to render a UI component only if a certain condition is true. Perhaps a feature is enabled for a subset of users only (such as administrators or premium accounts), or maybe part of the UI can be toggled on and off in the app’s settings. I’d like to share a few different ways to conditionally render a component with React and JSX that I’ve found useful. Which one you should choose depends on the situation. Sometimes, one approach may be the best tool for the job. Other times, it may come down to personal preference.

Getting started

Let’s consider a basic web app layout. We have a main content section and a sidebar. There’s also a special toolbar that only administrators should be able to see. This toolbar should be hidden from regular users.

For our code examples, we’ll be inside the render() method of the parent component of these three sections. Here’s what it might look like without any conditional rendering:

render()
{
    return (
        <AdminToolbar/>
        <PrimaryContent/>
        <Sidebar/>
    );
}

Let’s assume that this parent component has a prop named isAdmin that we’ll use to figure out if AdminToolbar should render or not.

Now that we’ve established our context, let’s look at our various options for conditional rendering.

Approach 1: A variable and an if() statement

The most basic approach is to store an instance of our AdminToolbar component in a variable, but only if the isAdmin prop is true.

let adminBar = null;
if(this.props.isAdmin)
{
    adminBar = <AdminToolbar/>;
}

Then, we’ll use our ability to embed expressions in JSX to reference the variable inside our JSX.

return (
    { adminBar }
    <PrimaryContent/>
    <Sidebar/>
);

React has no problem dealing with a null value in JSX. If the adminBar variable is null, React will skip over it without rendering anything.

Approach 2: Return null from component’s render() method

In the next example, we leave it up to the AdminToolbar component to decide on its own whether to render or not. Let’s give AdminToolbar its own isAdmin prop so that we can pass our value down one more level:

render()
{
    return (
        <AdminToolbar isAdmin={this.props.isAdmin}/>
        <PrimaryContent/>
        <Sidebar/>
    );
}

Then, in the render() method of the AdminToolbar component, we’ll return null if the prop is false.

class AdminToolbar extends React.Component
{
    render()
    {
        if(!this.props.isAdmin)
        {
            return null;
        }
        return (
            <div>
                { /* omitted for simplicity */ }
            </div>
        );
    }
}

Similar to the previous example, React understands that it shouldn’t render anything when a component returns null from its render() method.

Approach 3: Ternary operator (a ? b : c)

As a final option, we can take advantage of the shorthand nature of the ternary operator to embed everything directly inside our JSX:

render()
{
    return (
        <div>
            { this.props.isAdmin ? <AdminToolbar/> : null }
            <PrimaryContent/>
            <Sidebar/>
        </div>
    );
}

In my opinion, this approach works best when we don’t have any props to pass to the component (or a very small number of props) because we can keep everything on one line. Usually, if we need to pass many props to a component, it’s better to place the props on multiple lines. A ternary operator gets more difficult to read in that situation, and I don’t think it’s particularly obvious how to format this type of complex expression over multiple lines.

This following example seems somewhat reasonable, I guess:

render()
{
    return (
        <div>
            {
                this.props.isAdmin ?
                (
                    <AdminToolbar
                        name="Administrator"
                        needsUpdate={true}/>
                )
                : null
            }
            <PrimaryContent/>
            <Sidebar/>
        </div>
    );
}

However, I don’t think I’d be happy with this formatting if the condition were longer — with multiple parts separated by && and || operators. Similarly, if the final expression were more complex than null, I feel like the mental overhead to understand how each expression fits into the ternary operator would become too high. In my opinion, if the ternary operator needs to be split into multiple lines, our first approach (with the variable and an if() statement) is a much better choice. For one-liners, though, the ternary operator is a pretty compelling option.

One, two, or three?

Which approach do you prefer? Do you know about any other ways to conditionally render a component in React and JSX? If I forgot to include anything, let me know in the comments!