When React first appeared on the scene, one of its most compelling features was its “one-way data flow”. This is still outlined in the React docs under the page “Thinking in React”
The component at the top of the hierarchy will take your data model as a prop. If you make a change to your underlying data model and call
root.render()
again, the UI will be updated. You can see how your UI is updated and where to make changes. React’s one-way data flow (also called one-way binding) keeps everything modular and fast.
The idea is that data can only flow one-way through your app which therefore makes your app much easier to intuit, understand, and reason about.
This came to be summarized in the phrase: “UI is a function of state”, or ui = fn(state)
. Whenever some state changes, due to an action, the view re-renders. To date, a number of sophisticated “state management” solutions have been created to facilitate building applications with this mental model.
The rarely-acknowledged problem here, however, is that this “one-way data flow” is a bit of a misnomer. It’s really a one-way data flow on the client. But having data exclusively on the client is rarely practical. Most of the time you need to persist data—to sync it—which means you need data to flow two ways: between the client and the server.
A lot of state management tools only help you manage state on the client but they don’t help you effectively cross the network chasm: the gap between the state on the client and the state on the server.
Enter Remix: “one of the primary features of Remix is simplifying interactions with the server to get data into components.” Remix extends the flow of data across the network, making it truly one-way and cyclical: from the server (state), to the client (view), and back to the server (action).
When it’s said that your “UI is a function of state”, a more nuanced way of disentangling the assumptions in that statement would be: UI is a function of your remote state and your local state. In a traditional React app, all state lives on the client and the pieces you want to persist must jump outside “the one-way data flow” and be synced across the network to a server. As you can imagine, this is an area prone to bugs.
In Remix, however, the idea of "UI as a function of state" is transformed because remote state can be more easily disentangled from local state. “What’s the difference,” you ask? Think of it this way.
Remote state is any data that needs to persist, like user data. This state (e.g. how many unread notifications does the user have?) is stored off the client and reconciles into your app from Remix mechanisms like loaders and actions.
(Note: Remix helps you cross the network chasm by also providing stateful information around the transmission of your persistent data via transitions and fetchers — no need to track the status of every network request yourself via booleans like isLoading
or enums like initial | loading | success | failed
).
In contrast, local state is ephemeral data which can be lost (e.g. via a refresh) without negatively impacting the user experience. This state (e.g. is the dropdown open which reveals the user’s notifications?) is stored on the client via mechanisms like React state or local storage. Importantly, it does not need to persist and sync across the network thereby reducing complexity and the potential for bugs.
Forms, fetchers, loaders, actions, these are all “state management” solutions in Remix (though we don’t call them that). They give you the tools to keep persistent state in sync between the client and the server, ensuring data flows cyclically one-way through your app and across the network: from loaders to a component to an action and back again.
With Remix, your UI becomes a function of state across the network, not just locally. An interesting analogy to Remix’s data abstractions is React’s virtual DOM abstraction.
In React, you don’t worry about updating the DOM yourself. You set state and the virtual DOM does all the diffing to figure out how to make efficient updates to the DOM. Remix extends this idea to the API layer for persistent data.
In Remix, you don’t worry about keeping client-side state in sync with the server. You “set state” with a mutation and the loaders take over to refetch the most up-to-date data and make updates to your component views.
Hopefully this helps illustrate how Remix drastically helps reduce the amount of complexity required to build better websites. As Kent says in his talk at RenderATL, because Remix works before JavaScript that’s a win for your users because they get an experience supported by progressive enhancement. But it's also a win for you as a developer because you don’t need to build all the complexity traditionally coupled with state management solutions.
You don't have to worry about application state management when you use Remix. Redux, Apollo, as cool as those tools are, you don't need them when you're using Remix because we don't even need client-side JavaScript at all for the whole thing to work…[think about the app that you’re building] and pretend that you can throw away all the code that has to do with application state management…that’s what it’s like when you work with Remix. If it can work without JavaScript in the browser, that means you don't need anything in the browser that requires state management.