React — Separation of Concerns
BY ANDREI CALAZANS
Follow on twitter
A deep dive into React
Data colocation has been part of React since the beginning. From the early days until now, Component State has always been a way to colocate data with components. GraphQL Clients also pushed on this idea, Relay, for example, has a Fragment Container plus data masking approach, each component can declare its data requirements and only receive what it required. Data masking will hide data required from other child fragments.
Despite these tendencies, Redux and Mobx moved towards a separate idea. Domain logic separation from components. Make the components as dumb as possible which essentially meant not having any Domain fetching logic in it.
One can comment that GraphQL Client like Relay, also achieves separation of concerns. It abstracted the network requests and you essentially only declare your data needs through a selector, in Relay's case a GraphQL Query. Also, its mutation commit API is not tied to injected props. Apollo has less separation, mutations are expected to be done inside the Components and their FaCC¹ approach also enforces this.
There are two relevant parts to every Client Software. A User Interface and the Domain Logic. The former is everything you interact with, the modal, screen, and tooltip. The latter represents everything related to your busiess. The data you are fetching and normalizing about the User or Friends are part of your domain logic. The validation of user input is a domain logic, however, the state you hold that decides that the input should be red because it is wrong is a UI logic.
If the goal is to separate than let's review the available approaches. Imagine we represent each layer with the following color:
By using just React. You are essentially holding State at the same layer you are rendering. A very common approach is to fetch user information on the first render and store this data locally.
The above approach mixes Domain logic with UI Logic. Any changes to Domain logic would require direct changes to the UI logic, exposing the UI to possible regression bugs. There is however a way to avoid this. Which would be by Containerizing the domain logic, thus, wrapping your UI components with stateful Components. Giving us then the following representation:
Container versus Presentational approach has been largely discussed by the community. However, there seems to be a misunderstanding of the current changes made by React. By adding hooks, the community interpreted that React Applications should now keep Domain logic inside the components, next to the UI logic. This misunderstanding can be evidenced by this tweet:
David K. also mentions his observation of how React currently encourages Domain logic coupling with UI logic.
The addition of hooks did not prohibit the Container approach. Matter of fact, Hooks did not add any new functionality to React. It only changed the API Surface.
The problem is not the approach. But rather the lack of understanding where the line crosses. Most approaches either directly leak Domain logic into the UI or allow you to do so. Composing the following structure:
Here we have Domain Logic leaked inside the UI and also inverse direction — UI logic leaked into the Domain. What are examples of leaked logic?
By Injecting Props
Imagine the following: One can have some data about the result of a quiz. The goal is to render the right and wrong questions. By checking the right and wrong questions inside the UI rendering logic you are leaking your Domain logic. Your UI should only worry about rendering, not validating the data.
Although Redux provides ways to avoid the direct use of dispatch inside your component. Directly calling dispatch inside your UI layer, you are essentially leaking your Domain logic. Instead of calling dispatch(getUser(payload)), the UI should only worry about a *getUser *function. Better yet would be if your UI only worried about User data.
GraphQL Clients like Apollo and Relay both leak Domain logic into the UI layer. Relay, injects a relay prop which allows you to directly call relay functions. Apollo, injects a client prop with similar features. There are alternatives to avoid this leak, nonetheless, the existence of it makes most implementation naturally leak the logic.
By Lifting State
The inverse leak — lifting up logic to a domain container from the UI layer. The separation of concerns can confuse Developers. Separating Domain logic form UI does not mean UI does not have any state. State related to a View should be inside the View.
When following Software Development principles, many overlap with the separation of concerns. The goal is to achieve a scalable and maintainable codebase. The fact is we want to change code, add features, and feel confident of the work we have done. Needless to say that software is the result of a very flexible composition of written syntax. It allows you to write it any way you like. Therefore, there won't be a right or wrong case. But rather a suggestive path you can take.
 FaCC — Function as Child Component. Commonly called Render Props.