Literalism is the signature of uninspired engineering.
It’s what happens when your experience and vision is no match for the potency of a name and you find yourself one-shotted by tutorials and examples.
This is especially problematic in React, where vanishingly few examples of good code exist in the wild. Basically everything you’ve ever seen is bad. Everything AI models have seen is bad.
The Problem
Consider a scenario where you have an app that uses geofencing to determine whether certain features are available to the user. When the user leaves the geofence, certain features should be dismissed or disabled, and some backend state should be updated.
The likely code you or an LLM will generate will use some combination of polling, useState, useEffect+fetch or useMutation, and navigation state.
Congratulations. You’ve successfully created an emergent “random state factory”. Expect to deal with an endless stream of operations issues coming from frontend bugs emerging from non-deterministic behavior downstream of non-deterministic UI state.
This illustrates the chief “worst practice” of React: using UI state to drive business logic.
Think critically about this pattern. One of the core axioms of React is that UI should be a function of state:
UI = f(state)
But what you’ve actually built is a system where both your UI and your business logic are functions of your UI state:
[UI, state] = f(UI->state)
This inverts the entire model.
The Solution
Instead, use a state management layer to approximate a state machine representing your business logic.
Your app state at any time should be a member of a discrete set of valid states, which means state transitions should be deterministic.
Build stores that represent your business logic and state. Each store should have a reasonable set of state transition functions for its domain.
Your views should render UI representations of the state of domains they depend on, and should trigger state transition functions as appropriate.
What Good Looks Like
How do you make your app state discrete?
Express its type as a discriminated union.
For example, if you have a multi-step auth flow where you validate user credentials, obtain a JWT, and then have the user select a project and enter the project auth pin (which requires the JWT):
type AuthState =
// Initial
| {
type: "unauthenticated";
}
// User has entered correct credentials
| {
type: "credentials-validated";
jwt: string;
}
// User has selected a project and entered the project auth pin
| {
type: "project-authenticated";
jwt: string;
projectId: string;
};
Now, write a store that returns an authState
property of type AuthState
, and implement functions like validateCredentials
and authenticateProject
that transition the state to the next valid state.