React Router and Remix introduced automatic revalidation of data after mutations. I believe this is a paradigm shift and boosts productivity like nothing else available today.
In this article, I'll show you step-by-step why this matters and how to get out of what I call "state management hell" with the help of React Router or Remix.
Imagine you have a React SPA with a top bar. It shows the avatar of the signed-in user in the right corner or a "Sign in" button if there's no current user.
Your login page lives at "/sign-in" and includes the top bar and a form with email and password right below the top bar. After a successful login, the user is redirected to the home page at "/".
Now, imagine you are not using a library to handle routing. You have a simple router component that monitors the window's location and decides what to render based on that.
Because you know all pages will render the top bar, it lives in a separate component that is rendered higher in the hierarchy before your router component.
What happens after a successful login? The router component detects the change in the location and renders the homepage component. But there's something wrong.
Since the top bar is above the router in the render hierarchy, it will not be re-rendered. Thus, the user avatar will not appear, and the UI will be stale. You have to reload the page to see the avatar. I bet you're thinking of an app with this kind of bug right now 🙃
There are several possible solutions to this problem, and we'll talk about how React Router and Remix address this in a minute. But before that, let's look at the most common approach today: centralized app state.
You add the current user state to a React Context or state management library, read from it on the top bar, and write to it after a user signs in. Done. No big deal, right?
Wrong. It's a huge deal. You now have to make sure all devs in your team keep the structure of the state in mind and remember to update it when necessary.
More importantly, you must spend a lot of time designing the state. In the example above, we only need to know the current user. But a real app has hundreds or thousands of pieces of data as part of the state.
You have to constantly make the trade-off between separation of concerns and centralization in state design. If you put everything in one place, the devs need to keep a big structure in mind at all times.
If you have separate states (auth, clients, orders, etc), the team should remember which states to update after each mutation. Damned if you do, damned if you don't.
As apps grow, most bugs are related to state management. Diagnosing, fixing, or preventing them becomes costly, with the application state adding a ton of cognitive load to teams.
New features take longer to be delivered and often break unrelated functionalities. Separate teams need to coordinate closely even when working on different domains.
This is what I call state management hell!
Enters automatic revalidation
So far, we've been only talking about the state that lives in the browser. But if our app needs to store and fetch data from any kind of server, it also has a state on the server side.
In our example with a centralized state, both the browser and the server need to know who's the current user. Once we sign the user in, the server stores its state somewhere, and the React SPA somewhere else.
And then, every time we change the server state, we also have to update the browser state. Having two sources of truth is the origin of our state management hell.
Whenever we have two sources of truth, synchronization issues emerge over time. But unfortunately, we cannot avoid having separate browser and server states in today's world.
Due to the nature of web browsers, the only way to have a single source of truth is to do a full page refresh every time we make a change to the state. If we do that, the state lives 100% on the server.
But it's the 2020s, and we need a better UX than that. That's where abstractions like libraries and frameworks are useful. If we can't avoid having two sources of truth, let's at least hide this complexity from developers most of the time.
That's what automatic revalidation does. It lets us code as if there were a single source of truth, even though behind the scenes, there are two.
Let's walk through our example again, but now it uses React Router 6.4+ or Remix. Our top bar is rendered by a root route that will have all other routes nested inside of it.
Instead of relying on our custom application state, we'll use a loader on the root route to fetch the current user from the server and display it on the top bar.
Then, on the sign-in route, we'll use a Form and an action to sign the user in. The action does the authentication and redirects to the home page.
Because we used an action, React Router knows we are probably changing something on the server state. Then it does something magically simple: it runs all loaders on the page again.
That makes the root route re-fetch the current user from the server and re-render, now with the correct avatar at the top bar.
By assuming that if we're running an action we probably want to re-fetch all data on the rest of the page, Remix and React Router are solving our problem in the best way possible: by making it disappear!
Of course, there are exceptions. Sometimes our loaders take too long to run, and we want more granular control over what gets revalidated.
All of that and more is covered by both frameworks. Think of automatic revalidation as an excellent default that you can use for 90% of use cases and override whenever you need to.
But even if you have to do a little state management for 10% of your use cases, you're out of state management hell. Welcome!
Top comments (7)
But React was created to avoid just these issues?!? You are just back at a point where good old OOP had far better solutions to.
I also miss the good old mental model before all this JS complexity, @efpage! And the productivity we used to have! 🤓
But unfortunately, I want to build better UIs than we could back then. Remix and React Router, with the help of automatic revalidation, are the closest we got so far to feeling like in the good old days while providing 21st-century UIs!
Good to hear you know this times. I came relatively late to use web technology and was kind of shocked, how low developers performance was. I had not used a text editor for programming sind Delphi 2.0 was released in 1996.
From what I can see, it is possible to build appealing better UI´s even without the overhead you get from Angular or the React ecosystem. Maybe also, things have changed a bit since the browser war is over. The React ecosystem get´s more and more complex every year, while Javascript has become faster and more mature. Ok, the JS-class model is still a bit limited, but we can adopt some FP-patterns to make our code more reliable.
I started web programming about three years ago, and as I could not find a suitable platform for me, I created my own one. Applying some of the good old principles seems to make sense and I have created some nice projects that work well. Maybe for curiosity, you like to check out.
You find more details on the platform here (Still the old version, a streamlined one is about to be released soon). And this is one of my latest projects. This is is a very typical page, so it seems quite usable for me. Alls frontend and backend was made with DML in a relatively short time without any additional framework.
Very cool work! I also come from a Delphi background, and I was also shocked when I moved to a text editor to do web dev in the early 2000s 😱😂 I still miss the productivity we had with Delphi (don't miss the memory leaks, though!)
For websites (more information, less interaction), I think old-school HTML/CSS, maybe enhanced by a little JS and a little server-side code, is 👌🏽
I work with lots of web apps (less information, much more interaction), and for those, I think you should give Remix's tutorial a try and see how it feels: remix.run/docs/en/main/start/quick...
Thank you for the link, I´ll give it a try. Hope it is a step forward for me.
Anyway, I´ll continue to check out what´s possible with my own library and to grow some community around it. Currently I can say that it´s fun to use (at least for me), I can do a lot with very little code and results are pleasing, so it seems to be worth to continue. But I would love to have a better ecosystem that makes live easier.
I only had a very short look on Remix, can´t even imagine the value as I never really worked with React. Luckily I´m not a web designer, I just use the browser as a programming platform. So I´m not depending that much on supporting toolboxes (while they are very welcome if they save time) and an ideal workflow.
One of my findings with the people using web technology was, that they often solve one problem by adding another extension that does exactly this, but brings two new problems. Maybe this is what they call a "technology stack" :-) ? Seems this is a bit a root cause to the struggle you describe with React today.
From My view, things now look like this:
If somebody one day will find that the Redux-System lacks some things or causes some issues, should we add another layer ontop?
I know that the web is like this today, But I´m trying to find out, how to make things simpler. My technology "stack" currently is:
The nice thing about this "stack" is, that it can solve some conecptual issues we inherit from HTML/CSS (e.g. global scoped ID´s).
Anyway, thank your for you advice. I whish you much success with your platform.
Hy Daniel,
does it make any sense to think about using Remix without React? On a micro-scale, using DOM-states or stateful web-components works pretty well (think of an input element, that can handle different physical units), but managing routes with vanilla JS might not be the best solution, specially if you think about nested routes. Maybe it´s something I could use Remix for?
On a long term I´m thinking about a solution, where you write a single code and decide later to run it on the client or on the server. Could be a bit "HTMX"-like, but not for HTML, but for Javascript.