Fallacies of React: Why so many React projects fail
If you use React, it’s easy to hire experienced developers, and you can use the best components available from a variety of sources, right?
Wrong. Unfortunately, this is not at all true, for two main reasons:
- React experience doesn’t translate across projects: React is used for a variety of very different projects, from small website enhancements to full-blown web applications. Further, every React application uses a different set of components, sourced from various vendors and OSS projects, and each set of components is used very differently. If you hire a developer who has used React, but hasn’t worked on an application similar to what you want to build, their experience usually doesn’t apply. Basically, if the developer hasn’t used the specific combination of components you plan to use, their “experience” is essentially tantamount to just knowing JavaScript.
- Mixing & matching components doesn’t work: the mix & match approach appears to work in some simple tests, which is misleading. The reality is, mixing & matching components leads to an explosion of “glue code”. Worse, it leads to having to repeat the same configuration work (server data binding, skinning, i18n, accessibility) 5 times over if you have 5 different sources for your components. Ultimately, with mix & match, a high-performance, high-productivity UI cannot be reasonably achieved.
To be clear: React is a solid technology, and there’s nothing inherently wrong with it.
The problem comes from misunderstanding the nature of React. React is a very low-level, general-purpose platform, and when you adopt it, you have not actually chosen the technologies you are going to work with, the vendors or projects you will be relying on, or the skills you will need from your staff. All you’ve really chosen is a programming style.
As long as you understand what React really is, you can use it successfully. But if you fall for the fallacies of React, your project will fail, as many React projects do.
This article will give you a clear understanding of what React is and what it isn’t, what issues can arise if you misunderstand React, and give you the right process to follow when adopting React, so that you can succeed with it.
What it means to “adopt React”
For a long time before React and Angular came out, there were a handful of vendors with complete component sets: Isomorphic, Sencha, Vaadin, Telerik, and Infragistics. People who needed to build web applications went looking for components and found the component set that best matched their requirements.
Then Google introduced Angular, and Facebook introduced React. Both technologies involved transpilation, that is, taking a special dialect of JavaScript that browsers could not directly execute, and using a “transpiler” to translate it into JavaScript that browsers could actually run. In the case of Angular, the special dialect of JavaScript is called TypeScript; in the case of React, it’s called “JSX” and allows HTML to be embedded into the middle of JavaScript code (not normally allowed).
Both React and Angular offered something more than a component set – they offered a special dialect of JavaScript – but as far as components, both technologies came with really, really basic components that weren’t really usable for a complex web application.
The assumption was that you would go and find components from various vendors and OSS projects.
Given this, what does it actually mean when you decide to “use React”?
When you adopt React, you have not yet chosen a technology. You have chosen a programming style.
Choosing a technology is an additional step, and requires a different process.
Choosing React components
One of the main drawbacks of web frameworks like Angular and React is their lack of complete component sets. After you adopt React, the next step should be to evaluate what components to use. If you skip this critical step, here’s what happens:
- Individual developers immediately run into requirements the base React components don’t meet, and go looking for components.
- Developers really do not want to have to come to management and ask for money, so most of the high-quality, well-supported component sets will never be considered.
- Developers are notoriously bad at evaluating longevity, so they will happily begin using a component from an OSS project that was abandoned 6 months ago, if it meets the needs of a particular screen.
- Developers will spend entire days trying to make low-quality components work, because when something almost works, developers are enticed to dig deeper: surely, the developer thinks, we just need some “glue code” or a little troubleshooting of the component’s source code.
So what’s the result?
- The project has 2-3 different grid components from different vendors, 2-3 different combo box/dropdown controls, several button/menu/ribbon types, and a variety of different specialized input controls.
- Weeks and weeks of time blown (in aggregate) trying to get various buggy components to work, and to work with each other.
- Every screen takes care of data binding and other common concerns in a different way (the grid loads data differently from the combobox, which differs from another grid).
- Developers cannot easily understand or modify each other’s code, because they are using different components.
- The overall codebase is bound by 3-5 different open-source licenses, and perhaps also bound by limited commercial licenses where it may be ambiguous whether your use is valid.
This is already a disaster, but this isn’t even the worst of it (which we’ll get to shortly).
You may think I’m exaggerating, but sadly, I’m not. I’ve seen this and worse, multiple times.
So how do we avoid this?
Adopting React: The “Component Czar”
Immediately after choosing to use React, you can either do an up-front evaluation of all the components you are likely to need, or you can do it “organically” as you run into specific requirements.
I prefer the former (up-front evaluation), but ultimately, either works.
Whether you use up-front evaluation or not, you need a “Component Czar”: someone who is a gatekeeper for adding components to your application.
That person needs clear guidelines for evaluating possible components:
- Longevity & support
- License type
- Compatibility with other components (e.g. same or similar data binding approach)
- Suitability for other use cases beyond just the use case currently being considered
This approach will avoid the total disaster I’ve outlined above. But there is still more to consider.
Hiring for React projects: by Application Type, not by Platform
React projects range from trivial website enhancements to full-blown web applications with desktop-level UI.
Let’s imagine you were hiring for a project that involves a true web application: business users, long sessions, large data volumes, complex validation rules, etc.
As compared to hiring a React developer who had never built that kind of application, you would be far better off hiring a developer who had built similar applications before but hadn’t used React to do it.
This is not even a close decision! Your average React developer has done some tree- or menu-driven navigation, some simple forms, and some layout work. This experience is quite irrelevant in a true web application, where you are dealing with things like editable grids with thousands of rows, complex filtering UIs, forms with validation rules that span multiple related entities, and so forth.
The developer who has built a complex web application before will be able to easily pick up React. The developer who has used React for some simple interactivity definitely will not be productive in building a true web application.
Secondarily: hire by component use scenarios
While less important than hiring by application type, it can be helpful for software engineers to have experience with the specific components that you are using.
However, here again, the usage scenario is really more important than the brand of the component. A given candidate may have used the same brand of grid you’ve selected, but may only have used that grid component in a very simple scenario.
Consider the example of needing to build a screen that involves complex inline editing inside a grid. It’s clearly more important that the candidate has implemented a similar screen, as compared to just having used a brand of grid that is capable of building such a screen.
Even though the usage scenario matters more than the component brand when hiring, your “Component Czar” should definitely be involved in the hiring process. That person represents your organization’s total knowledge about available components and your organization’s needs. If a candidate comes in and claims lots of React experience, but they stumble over questions about data binding in advanced grids, your Component Czar will (correctly) eliminate that bad candidate for you.
Mix & Match: It doesn’t work
The idea of mixing and matching components from multiple sources is so appealing. You get to use the best-of-breed component for each component type: the best grid, the best combobox, and the best date picker.
This appeal is so strong that roughly every 8 years, a new component framework appears that encourages you to mix & match components. And it does work, for simple scenarios: if you are adding some limited interactivity to a simple website, you can absolutely use a great flyout menu from one vendor, then a combobox from another vendor as part of a simple form. This works if a flyout menu and a simple form are all that you need.
But when it comes to true web applications – full-screen, data-intensive, long sessions, sophisticated users – mix & match has always failed, and always will fail, and it’s for the same reasons every time.
Let’s see why:
1. Inconsistent databinding / data loading
Many people think that React has a “databinding” framework. Not exactly. Databinding in React consists of binding UI components, such as form fields, to in-memory JavaScript data structures. Unfortunately, this is the most trivial aspect of databinding. The hard part of databinding is the network boundary: data paging, criteria, sorting, validation and caching involve careful coordination between client and server. In core React, these problems are almost entirely ignored – they are left to custom components to solve.
Hence, when you mix & match, and you introduce a grid component and a combobox component from two different vendors, each will have a different approach to loading, searching and validating data. Often, these are inconsistent and irreconcilable differences: one component expects certain fixed page sizes, and another expects to request arbitrary row ranges. One component has expectations around how server validation works which are nearly, but not quite, compatible with how another component expects to do validation.
This leads to needing to write a ton of “glue code”, and often, there is the need to create redundant data services, because different components have different expectations.
When you instead use a single component set with a consistent databinding model, both the grid and comboBox can accept the same API definition of data available from the server (called a “DataSource” in SmartClient, for example) – and neither needs anything further.
2. Inconsistent databinding: saving, validation, updating caches & performance
The problem of inconsistent databinding gets even worse when you consider the saving side of databinding:
- gathering and submitting data from multiple types of input components
- handling, displaying, and clearing validation errors
- updating caches (such as stale information held in other components)
This is another “glue code” nightmare, as you try to adapt components with different concepts of value management and validation to work together. This gets especially difficult in the presence of complex requirements like cross-field validation dependencies or types of validation that require asynchronous server requests (like uniqueness checks).
Then when you consider caching, the problem gets worse than just “glue code”: there is also a severe performance impact. When you have a single databinding model, components can freely and automatically share already-loaded data, and ideally, when data is saved, any component that is holding stale data is automatically updated. This is how things work in SmartClient’s databinding system, for example.
When you don’t have this, it’s not just a problem of wasting effort on glue code: performance also suffers, and suffers terribly. Enormous numbers of unnecessary requests are made, and the performance that is possible with a single, consistent, advanced databinding framework can no longer be achieved. To understand exactly why, and understand how an advanced databinding framework is critical to web application performance, please read this article.
4. Modal windows & pop-ups
All components in an application must coordinate on what is “in front” (called zIndex) and what is blocked (as a modal window blocks events to background components). When you mix & match, you end up with problems like a dropdown that appears behind the modal window it’s contained in, or components in the background of a modal window that should be blocked, but are not.
Typically, a specific scenario can be worked around by injecting zIndex information via CSS or some JavaScript hack, but then the same problem reappears in a different, more complicated guise: for example, this time, it’s a modal window launched from another modal window, and now, a dropdown in the secondary window appears behind the window it’s contained in. But perhaps this happens only the second time that dialog is used because a hardcoded zIndex was used to solve the first instance of the problem. And so on – the problems will keep reappearing.
Correct modality handling requires a central coordinator, which some advanced component sets have. When you have such a coordinator, modality becomes something don’t have to think about at all – it just works. However, in the absence of a central coordinator, modality is an area where subtle bugs just keep reappearing and reappearing.
4. Accessibility
For an application to pass an accessibility review (which is required for all public applications, and all applications potentially used by any government entity), you need keyboard handling to be perfect, reliable, and consistent across all of your components. You also need every component to have the correct ARIA markup, and have that markup automatically updated as the component changes state. Further, you need various APIs to customize the ARIA markup for components, since some components can take on multiple different semantic roles as they are used in different ways.
Speaking broadly, most OSS components barely consider accessibility, and most commercial components only consider it in the context of an isolated component. But true accessibility – what is required to pass a review – requires extensive coordination between different kinds of components.
When you have multiple component sets involved, achieving a passing grade in an accessibility review is somewhere between a nightmare and a true impossibility. This is especially so when you consider modal windows: in a modal window, there should be a closed loop of tabbing that never leaves the contents of the modal window. So with multiple component sets around, what system is coordinating that?
5. Skinning
Every component set has a different look & feel, yet ultimately you need your application to have a single, consistent look and feel. So, if you have chosen to use component sets from 5 different vendors, you will be trying to use 5 different systems to try to reconcile appearance across your 5 different component sets.
This can get extremely complex. For example, if you have a standalone form field, but also an editable grid which embeds form fields into grid cells, you’ll need those two scenarios to match on fonts, padding and borders, as well as rollover styling, focus styling, hint styling, error styling, and styling for hovers or notifications, including any icons used.
Aesthetically, the first result of mix & match is glaring inconsistencies that make your work look unprofessional. But even if you had unlimited resources to spend, the final result of a mix & match approach is always sub-par: subtle inconsistencies will be present, and even naive users will have an intuitive sense of lowered quality.
6. Internationalization
Similar to skinning, this is another “fix it 5 times” problem. Different component sets may have their own internationalization approach, and typically, components that are good in one area (for example, accessibility) fail in another (for example, styling).
Often, when you find a component that has cutting-edge, very pretty styling, that component will turn out to have no internationalization support at all, or have a terrible, incomplete approach to it.
7. Drag & Drop
When you have a single component set, you can drag & drop between any two components where it makes sense. When you have different component sets, you are down in the weeds, trying to figure out if there’s any way to reconcile two different frameworks’ approaches to this feature. The most common version of this problem we’ve seen is picking a tree component from one vendor and grid from another. Want to drag between those? No.
8. Inconsistent APIs & productivity
In a consistent component set, the more you learn in one area, the more effective you are in another. For example, in SmartClient, if you learn to customize our standalone form fields, those exact APIs are used to customize the editors for inline grid editing. If you know how to customize the columns of a grid, those exact APIs are used to configure multi-column dropdowns, and also menus. Our grid, with its header, optional search bar, and optional toolbar, is actually a subclass of our core Layout class. And so forth.
When you instead mix & match, you are learning to do the same thing 5 different ways. Every time you introduce another component or component set, you make the problem worse.
Mix & Match: Avoid if you want to succeed
As far as mix & match issues, the list above is still just the tip of a Titanic-level iceberg. Because Isomorphic offers consulting services on both projects using our technology and projects that don’t yet, we have seen everything.
In sum, there is the common saying that you get 90% done with a project, and then 90% of the effort is still to go. Well, yes – if you use the “mix & match” approach, you will definitely be in that situation.
How to adopt React successfully
React is a solid technology, but it is definitely not a complete platform for building web applications. To successfully adopt React, you need to do two main things:
- When hiring for a React project, you should consider first whether the candidate has built applications similar to what you want to build (in any web technology). Existing experience with React should be considered a small bonus, not a must-have.
- Once you’ve adopted React, you still need to pick which React-compatible components to use. Mix & matching component sets just doesn’t work for web applications, so the best approach is to find a single framework that meets almost all of your needs, then be very careful about introducing any other components. You can look through the list of mix & match issues above and use it as a checklist to make sure the additional components won’t cause serious issues.
It is actually possible to get to 90% done and really be 90% done. Start with SmartClient, use our best practices, utilize our expertise, and we will get you there!
About the Author
Charles Kendrick has been the head of Isomorphic Software for over 20 years. He is the Chief Architect of SmartClient and Reify. Both products are heavily used and very popular amongst the Fortune 500. He has successfully worked with and coached dozens of teams delivering high performance systems and is a pioneer in the field of web application optimization.