React is without a doubt one of the most popular front-end JavaScript frameworks / UI libraries around. However, it doesn't mean that it's the best or that everyone likes it.
Among some of the more technical reasons behind people disliking React is, surprisingly, one of its biggest features as well - JSX. An extension to standard JavaScript that allows you to use HTML-like syntax in your React components.
How such a recognizable part of React, one that clearly stands to improve readability, and ease-of-writing one's code can be turned into a con? Well, it all comes down to the separation of concerns.
Separation of concerns
Before we dive in, I'd like to explain exactly what separation of concerns is, not to leave out any nuances.
So, separation of concerns means having clear lines between different concepts/pieces of something. In programming, JSX is a clear example of ignoring this rule. No longer do we have a "template" describing component structure in a separate HTML file and its logic in a JS one, but both (or more if you're using CSS-in-JS) are mixed together to form what some consider perfect harmony, and others - uncontrolled chaos.
Personal preference
Alright, so mixing the "view" and the "logic" together brings about the disruption of the separation of concerns. But is that really bad and does that mean that you always have to keep your component's view and logic separately?
No and no. First off, a lack of separation of concerns isn't necessarily a bad thing. It's a matter of personal preference of a developer or a team, and other guidelines. You don't have to keep your logic and view separately. But if you do, it still doesn't mean that each one of them needs a separate file. Perfect examples of that are Vue Single File Components (SFCs) or simply pure HTML file with <script>
and <style>
tags inside them.
React hooks
Separation of concerns is one thing, and React hooks the other.
So, React hooks have been around for quite a while now (almost 2 years since stable release), so they are rather well-known and already "covered to death" by many other blogs and devs alike. But let's have a brief overview one more time.
React hooks allow developers to add state and use other special React features, inside functional components, as opposed to the prior requirement of class-based ones. There are 10 of them built-in (v17.0.1), each for handling different React features, from which only 4 are commonly-used (useState()
, useEffect()
, useContext()
, and useRef()
) and you can naturally create your own. And it's this one last bit of information that we're most interested in.
Custom hooks
While React hooks themselves should be somewhat well-known, the process of creating a hook of your own is a bit less likely.
You see, the built-in hooks are "more than enough" to built solid React components, and if not, there's almost certainly an open-source library of some kind in the immense React ecosystem that "hookifies" the exact functionality you seek. So, why bother with learning more about custom hooks if this isn't necessary?
Creating a hook
That's a fair point. Custom hooks aren't necessary to do anything, but they can certainly make your life easier - especially if you like separation of concerns.
But everything will come in time. First - how to make a custom hook? Well, it couldn't be easier. A custom hook is just a function that uses other hooks. It's really that simple. It should also follow the "rules of the hooks", which can be easily done if you're using ESLint and proper official config, but that's it.
To be honest, you don't even have to do any of those things - using other hooks is not required (but rather common), and if your code is of good quality, custom hook name starts with use, and you use hooks as intended (at the very top-level of React component), then you should be fine.
Examples
Here's a very simple hook that runs the provided callback every second (because I couldn't think of anything better 🙃):
const useTick = (callback) => {
const handle = setInterval(() => {
callback();
}, 1000);
return () => {
clearInterval(handle);
};
};
...and here's how you can use it:
const Component = () => {
const stopTick = useTick(() => {
console.log("Tick");
});
return <button onClick={stopTick}>Stop ticking</button>;
};
As for a hook that depends on another hook, here's one that forces your component to update without noticeable state change by using useState()
"in the background".
const useForceUpdate = () => {
const [value, setValue] = useState(true);
return () => {
setValue(!value);
};
};
...and here's a usage example:
const Component = () => {
const forceUpdate = useForceUpdate();
return <button onClick={forceUpdate}>Update component</button>;
};
As a side-note, it's worth saying that such force update usually shouldn't be used. Most of the time it's either pointless or indicates some potential errors in your code. The only exception to this rule are uncontrolled components.
Solution proposal
By now I think you see where this is going. No matter how pointless my examples were, both of them still share one advantage - they abstract logic away from the main component function, making it look cleaner as result.
Now, it's only a matter of scaling this idea up, potentially moving the resulting hook away from the component file itself, and voila! You've got yourself a pretty good separation of concerns - in React!
It might seem like a simple revelation, but I've only come to it a while ago, and using it in my React project since then I must admit - it's a pretty nice solution.
You might agree with me on this idea or not (leave your comments down below), but it doesn't really matter. I'm just presenting a potential strategy to arrange your code that I find pretty nice, in hopes that it'll help you as well.
Best practices
So, if you end up at least trying out such an approach in one of your projects, then I do have some "best practices" that I personally follow and that might be of interest to you:
- only apply this tactic if your component's logic takes >10 lines or has a lot of smaller hook calls;
- put your hook in a separate file, which ideally should have no JSX in it (
.js
vs.jsx
files); - keep your naming consistent - e.g. hook in
logic.js
orhook.js
(with appropriate hook naming as well, e.g.useComponentNameLogic()
) and the component itself inview.jsx
orindex.jsx
under a single folder, with optionalindex.js
file (if it isn't reserved for the component already) for re-exporting the necessary bits; - keep only the simplest callbacks and event listeners in the JSX file, and move the rest to the hook;
- if using CSS-in-JS library that deals with hooks (e.g.
useStyles()
) then place it in a separate file, or at the top of the component file if it's not too big; - remember to organize your hook's code correctly - separate some of it to outer functions, and maybe even smaller hooks, if the logic is reused across different components.
What do you think?
That's my proposal for implementing separation of concerns in React. Is this the best approach that you must use? Definitely not, besides there's no "best approach" at all. Again, I just discovered that this one fits my needs, and I wanted to share it with you in hopes that it might help you as well.
So, what are your thoughts on such an approach? Would you like to see more posts where I share some personal code style tips in the future? If so, let me know in the comment section below.
As always, for more content like this, be sure to follow me on Twitter, Facebook, or through my newsletter. Thanks for reading and happy coding!
This post was written with ease, made grammatically-correct, and cross-posted here within 1 click thanks to CodeWrite with its great editor, smooth Grammarly integration, and "one-click publishing". Try it for free, and use the code
first100
to get 20% off your subscription (only $2.40/month!)
Top comments (5)
I agree the proposal about the separation of concerns for React. Even today, it is still worth discussing~
I use React and Vue in my daily development, but I prefer React. Recently, I'm also studying how to separate concerns of React function components. According to my own development habits to talk about my point:
Reusability of custom hooks
I think that if there is a lot of logic code in the function component, it is necessary try to extract custom hooks, which is helpful to the visual isolation effect of the code;
But at the same time, I don't agree that some logic without reusability and less code should be extracted as custom hooks. Because sometimes the complete component code will look like a pile of fragments, and also needs development time to extract custom hooks. If you use typescript, you also need to design a separate type definition for the parameters of hooks. This kind of unnecessary abstraction sometimes affects the development efficiency.
About separate files
I agree with the separation concerns, but I don't agree that the separation of files is necessary.
If a lot of files are separated, some people will have unnecessary mental burden in the continuous file switching operation. For example, in the vscode IDE, a large number of code files with similar names may be opened at the same time, and we often need to spend the cost of sorting out the relationship between these files in our mind.
So I prefer the implementation SFC of Vue: the components look highly cohesive in one file, but the concerns are still very clearly separated.
As a person who has used both React and Vue, I use TypeScript to developed a
full typings similar SFC solution in JSX/TSX environment for React function components 🤪. It is similar to the setup function of Vue3 in that it achieves separation of concerns in a natural way, and production ready:
github.com/joe-sky/jsx-sfc.
I don't think separation of concerns is definitely against React design philosophy. If we can improve the development efficiency and maintainability, it is worth exploring and practicing.
I'd say that personally, I'm not as much after the separation of concerns itself, rather than just making my code cleaner and more maintainable. Separation of concerns is just one of many ways to achieve that.
It's interesting that you've brought up Vue 3, as it recently became my go-to UI framework. I love it but not because of the separation of concerns and SFCs, but actually the complete opposite!
I came to love the
setup()
method and the Composition API. To me, they're like React Hooks, which I really like, but better. The reactivity model and one-timesetup()
execution seem much more pleasant to work with for me. On top of that, better JSX and TypeScript support led me to not that good of separation of concerns, but much better development experience and efficiency, combined with clean and maintainable code.If you're interested in this topic, I wrote more about it here:
Vue with TSX - the perfect duo!?
Arek Nawo ・ Mar 2 ・ 8 min read
Thank you for your reply, I will also read your Vue post. Seriously, I've also consider of the custom hooks idea for the main points of this post.
My situation here is that most projects use React, and I am also a loyal React user. Vue3 is very good now, but JSX/TSX support is not as rich as react ecosystem. For example, there is no stable CSS-in-JS solution(e.g. styled-components、Emotion) for Vue3.
The React SFC inspiration is actually to solve my pain points in the development, I haven't promoted it yet and I'm not sure how many people will agree with it. Separation of concerns is really just a way, but it is very successful in Vue. In the React environment I want to combine the separation of concerns with the existing TSX/eslint/CSS-in-JS ecosystem in a new cohesive way, and no string template is required.
If you take a closer look, you will find that I am not completely imitating Vue's SFC. Of course, different people have different needs, so their understanding will be different, it's perfectly normal.
I did take a look at your library. The API seems interesting, though I can imagine your potential users would really have to know what they're after in terms of structuring their code.
As for Vue 3 - you're right that the ecosystem is lacking. I get around that in my projects as I recently stopped using CSS-in-JS in favor of Tailwind. And although there are a few pain-points, it's a fairly good solution. But I acknowledge that React ecosystem has better tooling available even for Tailwind, which you can in this blog post (another plug 😅):
Tailwind JSX and class composition
Arek Nawo ・ Mar 3 ・ 8 min read
Why create one when you can get all awesome hooks in a single library?
Try scriptkavi/hooks. Copy paste style and easy to integrate with its own CLI