Separation of concerns with custom React hooks

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.

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 it’s 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.

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.

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.

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 or hook.js (with appropriate hook naming as well, e.g. useComponentNameLogic()) and the component itself in view.jsx or index.jsx under a single folder, with optional index.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.

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!

Author: admin

Leave a Reply

Your email address will not be published.