DEV Community

Discussion on: What happened to Components being just a visual thing?

Collapse
 
peerreynders profile image
peerreynders • Edited

And now for a completely different take (continuation of the UI as an afterthought theme).

Redux maintainer Mark Erikson observed (2018):

I've noted that React devs can often be classified into two groups: those who see the React tree as their app / have a "component-centric" view, and those who view their state / logic as their app / have an "app-centric" view.

Hypothesis: Visual Components require an "app-centric" view.

Kent C. Dodds: Application State Management with React (2020):

React is a state management library

This is a "component-centric" view. Essentially React is the foundation of the client-side app. This represents the mainstream use of React. Eventually the article gets to using Context for distributing state:

function CountProvider(props) {
  const [count, setCount] = React.useState(0)
  const value = React.useMemo(() => [count, setCount], [count])
  return <CountContext.Provider value={value} {...props} />
}
Enter fullscreen mode Exit fullscreen mode

However Sebastian Markbåge points out:

My personal summary is that new context is ready to be used for low frequency unlikely updates (like locale/theme). It's also good to use it in the same way as old context was used. I.e. for static values and then propagate updates through subscriptions. It's not ready to be used as a replacement for all Flux-like state propagation.

via Why React Context is Not a "State Management" Tool .

State management solutions like Redux and Mobx don't pass state through the Provider (like the above example) but a static value that makes it possible to subscribe to any updates of state. By extension this creates the opportunity to inspect the updated state before modifying React component state which would trigger a re-render. The process of state selection is often less computationally expensive than (unnecessary) re-renders.

At the end Application State management with React does suggest Jotai as a potential separate state management solution (a minimalist alternative to Recoil). However in contrast to Proxy (Mobx/Valtio) or Flux-based (Redux/Zustand) state management solutions, Atomic state management can be simply "sprinkled" in-between components - i.e. React is still the center of the universe.

In my view for React components to become predominantly Visual / Presentation / Dumb components, React's role needs to be constrained to managing the UI and therefore "React state" should only be "UI state".

Application state and the effects that manipulate it need to be at the centre of the client-side application - not the UI and much less the framework that implements the UI. The UI is simply the user's window into application state which may trigger effects that modify application state.

Hypothesis: Hooks favour the "component-centric" view

Hooks have been around for two years yet Mobx React integration still relies on the observer HOC with the <Observer> component being to only other alternative. In fact there used to be a useObserver hook which has been deprecated - though there is a suggested workaround:

function useSelector(select) {
  const [selected, setSelected] = useState(select)    
  useEffect(() => autorun(() => setSelected(select())), [])
  return selected;
}

function myComponent({observableOrder}) {
  const latestPrice = useSelector(() => observableOrder.price)
  <h1>{latestPrice}</h1>
}
Enter fullscreen mode Exit fullscreen mode

Redux on the other hand does offer useSelector right out of the box but Mark Erikson wrote an interesting article contrasting the tradeoffs of HOCs and hooks - in particular:

  • HOCs promote writing plain components that receive all data as props, keeping them generic, decoupled, and potentially reusable. The downsides of HOCs are well known: potential name clashes, extra layers in the component tree and DevTools, extremely complex static typing, and edge cases like ref access.
  • Hooks shrink the component tree, promote extracting logic as plain functions, are easier to statically type, and are more easily composable. But, hooks usage does lead towards a stronger coupling with dependencies - not just for React-Redux usage, but any usage of context overall.

Elsewhere he notes:

useSelector, on the other hand, is a hook that is called inside of your own function components. Because of that, useSelector has no way of stopping your component from rendering when the parent component renders!

This is a key performance difference between connect and useSelector. With connect, every connected component acts like PureComponent, and thus acts as a firewall to prevent React's default render behavior from cascading down the entire component tree.

So while it's possible to use hooks to integrate React components with the rest of the core application, hooks seem to actually encourage bolting application fragments onto the UI component (making React the core/foundation of the application). In terms of decoupling, HOCs are superior but hooks are more convenient.

Hypothesis: "Component-centric" is the new Active-Record

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.

React Component: "A component that wraps part of the visual UI, encapsulates state and effects that interact with that state".

Active Record is a codified feature in Rails but has been maligned in many contexts as an anti-pattern:

Active Record has the primary advantage of simplicity. It’s easy to build Active Records, and they are easy to understand. Their primary problem is that they work well only if the Active Record objects correspond directly to the database tables: an isomorphic schema.
If your business logic is complex, you’ll soon want to use your object’s direct relationships, collections, inheritance, and so forth. These don’t map easily onto Active Record, and adding them piecemeal gets very messy.
That’s what will lead you to use Data Mapper instead.

Patterns of Enterprise Application Architecture, p.161

DataMapper for Rails never really got much traction presumably because it significantly increased the "initial-time-to-success" - ActiveRecord was already there and it was easy to get started. Later the application never got complicated enough to warrant DataMapper or development had already progressed beyond the point-of-no-return to adopt DataMapper so maintenance had to grin-and-bear the consequences.

I believe there is a similar dynamic with "component-centric" client-side applications. Using React for application state management is easy in the beginning which leads to React becoming the backbone of the application. This doesn't pose a problem for relatively small applications. However once the pain of "component-centric" design becomes obvious it likely would take a great deal of effort to pivot to "app-centric" design, leading to the existing approach being pushed beyond acceptable limits.

Other References:

Collapse
 
redbar0n profile image
Magne • Edited

I agree. Thank you for very well reasoned thoughts and thorough backing. As always.

I especially agree with:

React components to become predominantly Visual / Presentation / Dumb components, React's role needs to be constrained to managing the UI and therefore "React state" should only be "UI state".

It's interesting to note that Kent C. Dodds and the community currently separates between two forms of state: UI state and Server Cache.

Whereas where it's currently going is that the Server Cache is not just a cache, but also includes Offline state. The ideal model seems something like AWS Amplify DataStore, where the client app has its own state that it will automatically sync with the server. All the while the developer has a nice and simple API to deal with, and not directly concerned about caching, normalization etc. The only problem is Amplify DataStore's bloated bundle size due to its ties to AWS (Cognito auth esp.). So even though open sourced in principle, it's not really universally useful. It has to do with the server sync component and conflict detection needing a server-side counterpart.

This is attempted being solved by Offix DataStore (previously Offix Client, built on top of Apollo Client). Together with the counterpart Graphback, developed by the same people.

This makes the client effectively a part of a distributed system. With all the complexities that entails...

Markbåge seems to think the distinction / architecture doesn't matter:

I think this also plays into the question of "app" vs "component"-based mindsets. Is React your actual "app"? Or is it "just" the UI layer, with the real app being the logic and data kept outside the component tree? Both valid viewpoints. - Sebastian Markbåge, at twitter.com/acemarke/status/114900...

Whereas we think any such perspective will taint the entire client side app, for better and worse. Plus impedance mismatch of people thinking differently and trying to serve the same ecosystem and use each other's libraries.

As you said:

Application state and the effects that manipulate it need to be at the centre of the client-side application - not the UI and much less the framework that implements the UI.

The funny thing is, isn't this where we started: with MVC on the client? Presumably with dumb views. The problem was two-way data-binding, which React brought the Flux architecture to solve. But maybe it should be updated, so that Flux decouples the state from the View layer?

Like Surma did with react-redux-comlink in that presentation you shared. The funny thing is that Redux kinda enforces this separation of concerns between state and rendering But it has been shown to be way too complex for most people and use cases to be worth it in general.. Maybe there is a simpler way?