One of React’s greatest strengths is the ability to separate the view from the logic. I like to take it a step further and create two separated components, one harboring view, and one – logic.
On the first sight, this might sound like an overhead. Why should I create two files, when I can just utilize render
method. While true, this approach has many benefits. For starters, it allows two people to work on one thing simultaneously. One, a junior, can write styles and JSX markup, while a more experienced developer can write its logic handling. Another valid point is a separation of concerns – an entire view can be replaced without even touching the files with logic inside, as long as they use the same props.
So, how do I approach such thing? Simple, really.
First thing is, I define the state. The component should things, like – respond to mouse hover, distinguish its state between active and dormant and display text passed to it. Next, I write the logic. Most often this is a class with some internal methods:
// containers/Thing/index.js
import View from "@src/ui/Thing";
class Thing extends React.Component {
state = {
active: false,
hovered: false,
};
setActiveState = () => this.setState({ active: !this.state.active });
setHoveredState = () => this.setState({ hovered: !this.state.hovered });
render() {
return (
<View
active={this.state.active}
hovered={this.state.hovered}
onClick={this.setActiveState}
onMouseEnter={this.setHoveredState}
onMouseLeave={this.setHoveredState}
text={this.props.text}
/>
);
}
}
So much for logic. There can be a lot more, of course, but for the sake of illustration, it will suffice.
Next, the view.
// ui/Thing/index.js
const Thing = (props) => {
const {
active,
hovered,
onClick,
onMouseEnter,
onMouseLeave,
text,
} = props;
return (
<div
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={{
background: active ? "tomato" : "transparent",
color: hovered ? "yellow" : "black",
}}
>
{text}
</div>
);
};
Again, this is it. Very simple and concise.
This pattern works great in teams with junior and middle developers, as they can work on the target goal while not staying behind due to either lack of knowledge in more complex logic matters, or to lack of interest in writing views.
—
- A minimal example can be seen on CodeSandbox;
- Full example of this architecture can be found in my project.
Top comments (20)
I am learning about React, while keeping in mind an integration with Twig (Symfony), but also an eventual porting to mobile apps. So, it's a lot of things in different directions to keep in mind. Some wrote that Twig and React are not made to be integrated, because they overlap too much. I feel it does not have to be the case. After all, Twig can generate any HTML page and React is flexible in the way it generates javascript and its made to be called from an HTML page. I feel the separation you propose is a step toward such an integration. A Twig component can rely on javascript (thus possibly on a React component) for reactive communication with the user. Does it makes sense? A related question, in React, is the view part also reactive or only the content is reactive? I bet they can be both reactive and that might be in conflict with Twig. I have not covered porting toward a mobile app. That makes the situation even more complex. My bet is that only the content/logic part can be reused in mobile app. If that is true, it's another good reason for the separation. I am learning and hopefully what I am writing, which are guesses, make sense.
Hey Maharishi,
that's a very interesting question. If you want to integrate React with Twig (or any other view, for that matter), it is possible, but with an asterisk: Your component (in Twig) should contain only raw data in attributes, not the actual markup. For example, something like
That one you could grab on the JS side (by looking for
data-react-root
attribute), parse thedata-props
attribute and useReactDOM.render
afterwards. This is a method you'd use in somewhat transition period between server-rendered views (from Twig, Jinja or whatever) to an SPA rendered purely by React. A quick and dirty example can be found here: codesandbox.io/s/gallant-matsumoto... (checkpublic/index.html
, that'd be your Twig,App.js
would be any component(s) you have).That being said, if you really want to have your views generated by backend, try Vue. It's a very powerful framework that is made with such things in mind.
I was not much thinking about server vs client side, but I admit it's an important question. We can hope that in some future, using isomorphic architecture as needed, the system would decide for us these implementation details. As you know, even Twig can be rendered client side: github.com/twigjs/twig.js/. No, I was more thinking in terms of the language seen by the developer, irrespective of where and how it is compiled/rendered. I like Twig as a way to generate HTML, but I see that React is good for interactivity. I like PHP server side, but not as a templating language (even though it was first created for that purpose). I am not so keen with the idea of a universal language for these different purposes. That's why I was looking at Twig and React integration. I understand that you see a mix of both as a transition phase only. I would like to understand better why you feel it must be a transition phase? Why it cannot be the permanent way to develop? What's wrong with that? I looked at the example that you linked. It illustrates the interaction between a view and react in a very simple way. I like it. But, in this simple case, as, I am sure, you know, the interaction was not really needed. It would have been much simpler to do every thing in a fixed view. This illustrates my point. I want to code as much as is convenient in Twig and only use React for what it does well.
Hi Dominic,
In my opinion merging two ways of generating view is redundant. You have to work on two markups – one's in Twig, another in JSX. You would probably want different styling (either CSS Modules, Styled Components or something even different), different way of providing data (REST endpoints or GraphQL instead of MVC with Twig), most likely a separate bundling system. On top of that, you would require every front-end developer to be able to work with both Twig and React, which is not that common nowadays.
React is a library for building entire views. It's too big and complex for just "adding interactivity". For doing just that, use something smaller, or even better, JavaScript with no addons.
If you really want to use React to build views, ditch Twig and use Symfony as backend only.
Yes. What made me think differently was the description of the Limenius/react-bundle package:
If the whole point of React is to combine the markup organization together with the js organization, then what is the point of integrating it with Twig, which purpose is to do the markup separately? So, I understand what you are saying, but I am curious about what the Limenius team had in mind.
Well, Dominic, the only way is to try and see :-)
Thanks for your reply. I guess what I did not explain well is that I am looking for interoperability or open standard. In React, there is a coupling between UI components and logic components at the global organization level, which prevents taking one side without the other. This is a limitation for interoperability, as you pointed out in the case of Twig. You suggested that the content side (stateful component, etc.) can be done in vanilla js. Following this clue, I looked around and found this. But then, in my opinion, what we really want is an open standard to allow the use of this kind of tools with different templating systems that only focus on the static structure. Moreover, for me and others, interoperability is by definition a permanent aspect of a global architecture that relates systems, not only a quality temporarily useful during a gradual transition from one system to React, as in React's "interoperability".
And thanks again. This interaction is very valuable to me. Your replies were enlightening. It could be that an open standard at this level is hard to think and/or will be too restrictive. I guess that I am looking for an explicit opinion on this subject.
Great idea and a very clear explanation. So would you recommend to split every component in this way?
I guess the answer is no, the technique is nice but it does add "boilerplate" ... personally I'd do this only for larger or more complex components, or depending on the project/organizational context.
Hey leob, thank you for your comment.
This is generally a rule of thumb, but I think that consistency is an important factor. While splitting every single component adds boilerplate, like you said, it will make sure that you won't have to look in two places ("where is the logic, in the containers or components?") and you won't have to split the component when it starts to grow.
I do like the pattern, but in many cases I'd be inclined to split it later "when I have to". Right now it's as if you're in fact criticizing the design decision of React (and other similar frameworks) to lump presentation and logic together, i.e. you're giving React a "controller and view" architecture after the fact ... but again, I do like the pattern, and especially the way you're coding it - clean, elegant and simple, the only thing I don't know is if I would always do it right off the bat.
I don't think that splitting view and logic into separate files is against React design. It's been said many times that components doesn't have to emit JSX. Angular even encourages you to, having separate
.template.html
file.But all this aside, this is strictly an idea. I've been using this approach for quite some time and I found it helpful, but that's me :) Please, let me know with what you'll end up with, perhaps you'll have better idea and we will all win then :)
I agree that it isn't against React design, as you know React is in totally non-opinionated about these kind of things (unlike frameworks like Angular and, to a lesser extent, Vue), that's why these kinds of architectural patterns are so heavily discussed within the React community.
As I said, I do like the idea, I was only questioning the practicality (or necessity) of splitting every component this way. My feeling/intuition is that the pattern is most valuable for the bigger or more complicated components. The principle or the concept as such is totally fine with me.
(I don't really have a better idea, and in the React world as in other "worlds" there are many ways to approach these problems, I'm not claiming to be an expert in this area, far from it)
This looks like an Dependency Injection (or the Container) pattern where one would inject properties (
props
) so it'd make not only theThing
more customizable and dumb but also easily testable 🙂Did I understand it correctly? 😅
Hello Sung,
Yes, this is very close to what Dan Abramov told about. But not quite DI, as you'd need to introduce another layer, where you can explicitly pass the
ThingView
source. Right now it's coupled with its container.And yes again, this makes this approach very testable, as you can do UI testing with mocked data and don't worry about any logic behind it.
Thanks for the explanation Tomek 🙏
I can see how this pattern can work to even extract the component to publish on NPM to be used separately altogether 😮
Yes, like I said earlier, you can easily create an extra layer that will inject the view rather than use it implicitly via internal import.
What I'm wondering, as primarily a Java developer (but I started programming in, um, 1982) with some Angular experience, is there still a lot of difference between React and Angular once you add separation of concerns and various libraries to the former?
The reason why I'm asking is because I'm looking at replacing our ageing frontends, like those in JSF, by something a little more modern, and that basically comes down to either Angular, React or Vue. (Being able to run it on a mobile device is another consideration, so NativeScript seems the logical choice there.)
I know all about mixing presentation and business logic (need I say JSP?) and what it means if developers can mix the two: they will. And you end up with an unmaintainable mess. We do long-term projects with incremental updates, so maintainability is very important. That's my biggest concern about React.
On the other hand, Angular has a lot of qualities, but simplicity isn't one of them. And we do have limited resources, so getting everybody on board with Angular... I'm not sure that is going to happen.
Which leaves Vue, but I know very little about it.
I'm not looking for the best framework. What I'm looking for what's best for the company I work for. And I haven't figured that one out yet.
Hey Peter!
From my perspective, Angular is a great choice for enterprise-grade applications. If you have a really large team that can adapt this easily – go for it. Learning curve is steep, but it really is a framework. You're getting everything, routing, http service, whatever.
React, on the other hand, is more of a "all 'round" library, it works great with small companies (like the one I work for now) and big ones (like the one I was working for last year). But it comes with a cost – you need to choose (and stick with your choice) everything. HTTP module? Router? Application state management? All of these are vendors. React just renders the UI (and does it pretty great).
Separation of concerns as an architectural pattern is framework-agnostic and can be applied to either Angular or React. It's a leader's role to maintain the code quality on the big picture. If you, as a lead, decide that logic and view needs to be separated (and boy, they need to be!), you should stick to it and make your team understand why.
With all that being said, I'd go with React, since it has way smaller learning curve and can be scaled pretty well, while Angular is already scaled when you begin.
Personally, I put data in redux, business logic in saga, and my UI is a function of store values,
you give me my store, I'll give you exactly what the user is seeing,
which means I can do playbacks on bug reports, and do time travel debugging, track how the data is flowing etc.
Starting a React project now and I think I like your approach so far. It might be useful to update your post to explicitly say that View is an imported UI Thing. As the post is now, it is difficult to grasp at first glance why both things (heh!) are called Thing and where View comes from. Or change the code like in your sandbox project, which seems more natural: Thing and ThingView.