tech-dept
maintenance
react
stories
soft
lightweight
thoughts

"Who works in JavaScript ecosystem, doesn’t laugh at the circus". This article is more about reflections and shifting mental models, sharing stories rather than delivering high-tech insights.

Putting Users First In Web Development

How did it happen that developers, instead of focusing on the quality of the application (performance, stability, UX, consistency, etc.), started playing a game of who can produce better "stickers" in some advanced text editor – yes, I’m talking about code here.

There have been many books written on clean code, design patterns, and various kinds of good/bad practices, and an even greater number of articles and posts. The problem is that less experienced people have no idea how it all really works. The term "clean code" itself has become a huge buzzword. Everyone interprets it differently, so when you have a post, say on LinkedIn, with an example of some solution, you're sure to find a ton of comments about whether the code is clean or not...

The great absurdity is that people judge code based on its shape, appearance, the pattern used, or simply whether it suits them. Code is supposed to work, be stable, and easy to change/not tightly coupled. Everything else is irrelevant noise and clutter.

Of course, I’m simplifying things a lot because the topic is really complex. Writing code that is stable and works is not everything. It also has to scale and be decoupled from other parts of the system – these are more advanced topics. However, I want to dedicate this article to a phenomenon I’ve been observing for some time. Namely, how people harm themselves by writing code they don't fully understand, doing things they don't grasp, or simply ignoring user requirements and focusing on their "comfort" or sense of correctness when writing code.

Additionally, we’ll consider where the user fits into all of this, how much they suffer as a result, and how teams burn through tons of money on irrelevant work. We’ll also look at what the User First approach is, and go through some examples to get conclusions in the end.

JavaScript Developer Life Cycle JS Dev Life Cycle

If you're curious about how unnecessary refactoring can make your life harder, read Be Careful with Design Patterns.

The Modern Trap Of Constant Change

I hate moving. After all, who enjoys constantly running around with stuff? If you can afford a moving company, you're lucky. It's worse if you live in a building without an elevator (like me) and on the fourth floor no less. Who wants to keep hustling and doing the same thing over and over, only to move somewhere else right after? Exactly, imagine such a life. Moving every 3 months, and that's your life until the end. Doesn't sound appealing, does it?

This is exactly the kind of life our applications lead. We constantly have to "move," even though we don’t like it. Updating dependencies, changing requirements, refactoring, etc... All of this is often unnecessary; you just need to be aware of it and take it into consideration.

Someone once said, "If you're not growing, you're standing still" or "Change is a catalyst for growth". There's some truth to that, of course - I think both statements refer to the unhealthy situation where someone doesn't want to change anything in their life because they're afraid. But is there an alternative for people who change too much? Personally, I haven't come across anything like that. I think you're starting to see where I'm going with this.

Currently, in the world of web development, change is an everyday occurrence, not just "every three months" as I mentioned earlier. Do a little experiment - leave a project for a month. Come back, run npm audit and npm outdated, and right after that, go get some coffee and start playing with bumping dependencies. And so it goes, until the bitter end.

Outdated Dependencies List of Outdated Dependencies

Vulnerabilities After Npm Audit Vulnerabilities List

Everything is fine and dandy as long as there are no "unmaintained" libraries or the libraries you're using don't rely on such ones. However, if you encounter this situation, and it happens to me several times a year, you can easily compare it to moving. Man, how annoying it is. You had plans to work on a cool feature, but instead, you end up changing version numbers in some files with a .json extension.

Random Error During Audit Force Random Error During Audit Force Fix

Even if you run npm audit fix --force, there's no guarantee that you haven't broken something. Think about it - if there's a package you're using that relies on something with a vulnerability, and you have to bump that package's version by two major releases, there's a huge risk that something will break.

Sometimes you even find yourself in a hardcore situation where you absolutely have to upgrade a package (due to company policy), and it turns out that the app doesn't build anymore - because the peerDependency you just upgraded isn't compatible with the version of the package you're using. At that point, you have the following options:

  1. Look for an alternative and rewrite some code.
  2. Fork the repo locally, update it, and use npm link with a local install - if the repo's license allows it.
  3. Submit a PR to the official repo.
  4. Force the update and try to fix it in some magical way.
  5. Use overrides
{
  "name": "your-project",
  "version": "1.0.0",
  "dependencies": {
    "some-package": "^2.0.0"
  },
  "overrides": {
    "some-package": {
      "another-package": "3.0.0"
    }
  }
}

And you've done all this on your own. If you rely on packages that are widely used, the risk is significantly reduced, but if you start installing tons of random libraries, you suddenly fall into a trap. One deprecation could cost you a ton of time. That's why, when I see comments like "Why reinvent the wheel?" or "Why do you have your own modal or form hook?", it drives me crazy. Developers have become incredibly lazy and have turned into code churners, sometimes doing so many things thoughtlessly that it ends up killing the project.

Don't get me wrong - no one here is going to extremes and saying "don't install packages". Install them, but do some due diligence on what you're planning to install. Here's a list of things you should check:

  1. The size and extent of the vendor lock-in.
  2. The number of downloads and reviews.
  3. How much the code produced by the package impacts your code.
  4. Who else is using this package.
  5. And ask yourself - do you really need a package for this?
  6. Is the package using any risky dependencies?

One more thing. The worst trap is when you can't upgrade a package because another package's code is too tightly integrated with your application's core.

For example, take the combo of React + Redux. Redux affects the structure of an application in React so much that removing it or switching to something significantly different is very difficult. That's why there are still so many applications that use it. It's really hard to switch to something else without a complete rewrite. When you add a ton of other libraries that someone installed on top of that, it turns into quite a nightmare: redux-persist, redux-thunk, redux-observable, redux-saga, and so on.

BTW: Here’s the Zustand vs Redux article.

I'm simply trying to say one thing: Don't install packages thoughtlessly.

Every outdated package quickly becomes technical debt that must be constantly addressed. If you want to know more about dealing with technical debt, not only related to package updates, read the Dealing with Tech Debt in Applications article.

Stack Agnosticism In Web Development

There's a line from a Polish movie that goes something like, "Stick to the plan, or you'll regret it, do it or I'll rip your face off". The key is moderation. Only install what you truly need - I mentioned this earlier.

Recently, there's been a growing trend to avoid dependencies at all costs. Take ShadCN for example - it's not even a library in the traditional sense, but rather a CLI tool that helps you integrate components into your own code without creating the vendor lock-in.

You have full control over each component and can modify them as needed. The tool simply generates and versions the default components, but maintaining them is up to you. Of course, sometimes it adds a library or two, but there's a good chance you'd be using those anyway - like classnames.

The same approach is taken when we consider adding a new dependency. We start by thinking and doing a PoC to see how the code looks with and without it, and what the library uses under the hood. We analyze the impact carefully. This requires certain skills and experience, but in the long term, it's very healthy for the project.

Alright, let's dive into more examples of what this technological agnosticism - or rather, this approach - looks like. Below are some examples with explanations.

Ambitious New Developer

Someone joins the project, sees that you're using the Context API, and immediately suggests Zustand because it's "super robust" and so on. Now, let me share a real-life story. I had Zustand in my project, version 4.4.3. Running npm outdated, I saw there was a new version of the library with no breaking changes - 4.5.5. I went ahead with the update, and suddenly the whole app was broken. The only thing I got was an error during server-side rendering, saying that my state wasn't properly synchronized.

Zustand Error Error In Zustand During Build

So, I had to revert. The question now is: does it make sense to migrate functioning code to an external library that only offers different syntax, unknown performance changes, potential bugs, and adds another package?

If there's a real problem - like Context API not scaling well, issues with one Context depending on another, etc. - then maybe it's worth considering, but only after a good PoC.

Form Handling

Someone is asking: Why aren’t we using Zod, react-hook-form, and other tools for handling forms? The reason is that we currently only have two forms - sign-in and register - and our app is not form-based.

Components

Why did you create your own Modal component? This is a risky move. Modals may seem simple, but they’re not. Building your own can cause issues with device and browser compatibility. It's usually better to use a library or learn from existing library code.

If you're set on implementing one yourself, here's an article on: Creating a Modal with React and Tailwind.

Utils

Using the sort function from lodash is a good example of when to consider a more efficient approach. While it's a smart choice, ensure you're using lodash-es or install only the specific functions you need, like lodash.debounce.

Complex Requirement

This approach has exceptions. If you need a complex drag-and-drop feature or a chart library, it’s best to install a specialized library and do your research.

User First Approach

It only makes sense to mention this approach now. The idea is to evaluate every change made to the application from the user's perspective first and ask yourself - will it benefit them?

Does the user really need a complex tech stack that requires maintenance, or would they prefer a simpler one, which might result in better app performance (at least in theory, because results can vary)?

Next, is it worth doing a code refactor? What will it achieve? I'm talking about refactors that don't do anything other than change the structure or variables in the code - with no real, tangible benefit.

Maybe it would be better to spend that time refining the UX, writing tests to reduce the chances of regression. The main point is to always prioritize the needs of the users.

Following this approach, you'll likely gain:

  1. Fewer bugs from unnecessary refactors.
  2. Better testing of the app.
  3. Less time spent maintaining libraries.
  4. Less work that doesn't produce any real results.
  5. Less wasted money on unnecessary things.

Again, this doesn't mean you should avoid refactoring altogether. It's about pragmatism! Check if there's really a problem, measure performance, prepare an action plan, consult with others, and think about whether it's even necessary. Don’t act like a client who sometimes asks for something without knowing what they want to achieve. Just keep a healthy dose of common sense.

Now think about something as absurd as creating a micro-frontend architecture for two websites that only share some styles between them. I’ve seen such absurdities. It’s much simpler to just host the CSS files and use them in both places rather than adding the massive workload that comes with implementing micro-frontends. And all this for just a few components. Developers really need to think carefully about what they’re doing to themselves and the companies that employ them. It’s our responsibility to choose something reasonable.

It’s like someone who owns a home renovation company buying massive road construction machines. What’s the point?

If you need more information about the challenges with micro-frontends, check out the Be Careful with Micro-Frontends article.

Summary

In my daily life, I strive to be pragmatic in every aspect. I try to make decisions rationally, seek advice from others, read, analyze – all without going to extremes. I take a similar approach to programming. Every hasty decision can end in disaster, frustration, and problems in the project.

Absurd situations, like "massive month-long refactorings" that result in a total mess and worse performance, are standard in this industry. Think carefully before installing a package, do a PoC, measure and set goals before refactoring, and most importantly, consider: Do the users and clients of the application really need this?

Have you tried putting yourself in the users' shoes? I hate it when the app I’m using crashes. How many of these situations are caused by refactors (not needed ones)?

Author avatar
About Authorpolubis

👋 Hi there! My name is Adrian, and I've been programming for almost 7 years 💻. I love TDD, monorepo, AI, design patterns, architectural patterns, and all aspects related to creating modern and scalable solutions 🧠.