Why Web UI Development Is So Hard?

Juntao Qiu
ITNEXT
Published in
8 min readSep 12, 2023

--

Web UI development might appear straightforward at first glance, but delve deeper and you’ll discover a multitude of complexities that challenge even seasoned developers. This piece aims to dissect the inherent challenges of web UI development, from the discrepancy between web languages and the modern UI requirements, to intricate data management issues and asynchronous API calls.

We’ll also explore often-overlooked “unhappy paths” such as loading states, error handling, and broader architecture considerations encompassing security, performance, and accessibility.

Navigating Language Mismatches

Unless you’re building a straightforward, document-like webpage — such as a basic article without advanced UI elements like search boxes or modals — the built-in languages offered by web browsers are generally insufficient. Most web applications are much more complex than a simple document.

A “normal” document website

The disparity between the language of the web and the UI experiences people encounter daily is substantial. Whether it’s a ticket booking platform, a project management tool, or an image gallery, modern web UIs are intricate, and native web languages don’t readily support them. You can go the extra mile to “simulate” UI components like accordions, toggle switches, or interactive cards, but fundamentally, you’re still working with what amounts to a document, not a genuine UI component.

In an ideal world, building a user interface would resemble working with a visual UI designer. Tools like C++ Builder or Delphi, or more modern alternatives like Figma, let you drag-and-drop components onto a canvas that then renders seamlessly on any screen.

https://25.cppbuilder.dev/quick-history/ — An ancient IDE back to the last century for desktop applications.

This isn’t the case with web development. For instance, to create a custom search input, you’ll need to wrap it in additional elements, fine-tune colors, adjust padding and fonts, and perhaps add an icon for user guidance. Creating an auto-suggestion list that appears right under the search box, matching its width exactly, is often far more labor-intensive than one might initially think.

The issue view of Jira

Above is a Jira ticket — an issue view, and as you can tell it’s a relatively complicated user interface. For such a UI you might expect there is Navigator component, Dropdown list, Accordion and so on. But there isn’t, or should I say not directly.

Developers have worked hard to simulate these with HTML, CSS and JavaScript. If I disable the CSS for the site temporarily, I will get something like this:

Jira Issue view without CSS

Thanks to Jira engineers’ hard work, the application still functions even though it doesn’t look right — you can click buttons and links to navigate around and even upload an image as an attachment for the ticket.

Utilizing a design system library can alleviate some of these challenges, offering developers a head start rather than building from scratch. However, design systems come with their own set of drawbacks and require thorough evaluation before integration into your project.

Unfortunately, the language mismatch is only a tiny portion of the problem, and frankly, the situation has changed slightly since we have declarative UI libraries like React and Vue. However, there are other challenges in the front-end realm. The data (state) management is definitely on the list.

Understanding the State management

Managing the state in modern frontend development is a complex task. Nearly every application has to retrieve data from a remote server via a network. This often involves various concerns like authentication for API access and the specifics of the data-fetching code, such as the protocol used, data format, and whether the API is RESTful, GraphQL, or RPC-based.

But that’s just one aspect; there’s also the matter of managing local state. For example, an accordion component needs to track whether it’s expanded or collapsed, and a text field in a form must manage its own value.

A accordion component

Using a third-party state management library like Redux or MobX can be beneficial when your application reaches a level of complexity that makes state tracking difficult. However, this approach isn’t without its caveats and should be considered carefully.

Many developers are leaning towards using React’s built-in Context API for state management. Additional complexities such as steep learning curves, boilerplate code, and potential performance overhead are some reasons why these libraries might not be suitable for everyone.

Exploring the Unhappy Paths

When it comes to UI development, our primary focus is often on the “happy path” — the optimal user journey where everything goes as planned. However, neglecting the “unhappy paths” can make your UI far more complicated than you might initially think. Here are some scenarios that could lead to unhappy paths and consequently complicate your UI development efforts.

Errors in Another Component

Imagine you’re using a third-party component or even another team’s component within your application. If that component throws an error, it could potentially break your UI or lead to unexpected behaviors that you have to account for. This can involve adding conditional logic or error boundaries to handle these errors gracefully, making your UI more complex than initially anticipated.

For example, the following code we’re trying to access something doesn’t exist in the passed in props — a common TypeError: Cannot read properties of undefined (reading ‘doesnt’) and it throws an exception.

const MenuItem = ({
item,
onItemClick,
}: {
item: MenuItemType;
onItemClick: (item: MenuItemType) => void;
}) => {
// @ts-ignore
const information = item.name + item.something.doesnt.exist;

return (
<li key={item.name}>
<h3>{item.name}</h3>
<p>{item.description}</p>
<button onClick={() => onItemClick(item)}>Add to Cart</button>
</li>
);
};

It can cause the whole application to crash if we don’t isolate the error into an ErrorBoundary.

Wrap the could-go-wrong component with a ErrorBoundary

Downstream Systems Are Down

Your UI might depend on various microservices or APIs for fetching data. If any of these downstream systems are down, your UI has to account for it. You’ll need to design fallbacks, loading indicators, or friendly error messages that guide the user on what to do next. Handling these scenarios effectively often involves both frontend and backend logic, thus adding another layer of complexity to your UI development tasks.

Unexpected User Behavior

No matter how perfectly you design your UI, users will always find ways to use your system in manners you didn’t anticipate. Whether they input special characters in text fields, try to submit forms too quickly, or use browser extensions that interfere with your site, you have to design your UI to handle these edge cases. This means implementing additional validation, checks, and safeguards that can complicate your UI codebase.

Understanding and effectively managing these unhappy paths are critical for creating a robust, resilient, and user-friendly interface. Not only do they make your application more reliable, but they also contribute to a more comprehensive and well-thought-out user experience.

I’ve discussed the unhappy path in this article before, please have a read if you haven’t yet.

Accessing remote state over the network

In React, network programming poses unique challenges that go beyond the apparent simplicity of making API calls with useEffect. While the happy path might seem straightforward, the reality quickly complicates as you account for various scenarios like error handling, loading states, and retries. Add to that the complexities of caching and the code can quickly become unwieldy and hard to maintain.

So instead of doing this (it’s a good starting point, though) with useEffect hook.

const [users, setUsers] = useState<User[]>([])

useEffect(() => {
const fetchUsers = () => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => setUsers(data));
}

fetchUsers();
}, []);

In a real-world codebase, beyond just using the fetch function, you’ll often find yourself managing additional states, a process that’s also susceptible to errors.

const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');

useEffect(() => {
const fetchUsers = () => {
setLoading(true);
fetch('https://jsonplaceholder.typicode.com/users2')
.then((response) => {
if (response.status >= 200 && response.status <= 299) {
return response.json();
} else {
setLoading(false);
throw Error(response.statusText);
}
})
.then((data) => {
setLoading(false);
setUsers(data)
})
.catch((e) => {
setLoading(false);
setError(e);
});
}

fetchUsers();
}, []);

if(error) {
return <ErrorMessage />
}

There is a long list of potential considerations on the react-query documentation page.

Some challenges listed on the react-query page

Other Considerations

We’ve touched on some key challenges in Web UI Development, yet this is just a fraction of the complexities you’ll encounter in real-world projects. Many more factors must be considered as you navigate these and other challenges.

  1. Cross-browser Compatibility: Different browsers have different rendering engines, and even different versions of the same browser can behave differently. This makes cross-browser compatibility a major concern.
  2. Performance Optimization: Ensuring that the website is fast and efficient requires a deep understanding of how browsers render elements, lazy loading, optimizing assets, and much more.
  3. Accessibility: Building an accessible website that’s usable for all individuals, including those with disabilities, requires additional knowledge and testing.
  4. Security Concerns: With the rise of front-end applications, client-side security has become more crucial. Think about issues like Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), and data leaking through client-side logs.

While each bulletin above could warrant its own article or even a series of articles, I’ll stop here to keep this post from becoming too lengthy and overwhelming. I’ve included a references section with further reading materials for those interested in diving deeper.

Summary

In sum, the landscape of web UI development is fraught with challenges that extend beyond writing code and designing interfaces. The inherent language limitations, nuanced data management, async complexities, and often-ignored unhappy paths collectively make this a formidable field.

Architectural decisions around security, performance, and accessibility further complicate the landscape. Understanding these challenges is the first step towards navigating them effectively and crafting web UIs that are visually appealing but also robust, secure, and user-friendly.

References

If you like the reading, please Sign up for my mailing list. I share Clean Code and Refactoring techniques weekly via blogs, books, courses and videos.

--

--

Writer for

I help developers write better code. Developer | Author | Creator. https://juntao.substack.com/ @JuntaoQiu