Patrick’s Blog — Web development
10 habits I borrowed from python that I use in React (Part I)
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Choose your variable names carefully (Beautiful is better than ugly)
It’s easy to name a component “Container” or “Wrapper”. But that makes naming conflicts really hard to figure out, and more technically, it really annoys someone doing a search through the repository to see where are the other instances of that “Wrapper” for a search-and-replace task. Giving a more specific, context-relevant name to a variable makes it easier to read and to work with. Give it some thought!
Make your code as obvious as possible, but don’t exaggerate (Explicit is better than implicit)
One way that I like to do this (that aligns with the previous point) is to use the power of React Hooks that allows us to isolate functionality. One problem that always shows up is tracking some binary object, which can easily be done with a boolean. So what do you always end up doing? useState. It looks something like this:
But “True” and “False” are internal logic states, they’re not very expressive in terms of what the ExampleComponent is supposed to be used for, so it’s not clear what “setSelectedTrue” means in terms of functionality when reading this code. One would have to read the code thoroughly to figure out what happens. One thing that I like to do is to isolate such logic into a hook:
The “useBoolean” hook is a very re-usable hook (that I use all over the place!) to keep track of a boolean state. One thing that you often end up realizing by doing such re-factorings is that by naming your variables properly, you realize that your code didn’t make sense as well! In the original case, you could have reasoned “well if my ComponentOne is selected, I want to render those two, and if it is not, I only render ComponentTwo”. But by re-phrasing “if my ComponentOne is shown, I show it, otherwise I don’t, but in any case I need to render ComponentTwo”, it becomes much more obvious that there is no need for ComponentTwo to be rendered the same way the same way each time, and it sounds better when you’re thinking about it.
The other nice thing about using custom hooks to re-use functionality and returning the results as an array is that if the callbacks in the array are generic enough, it becomes very expressive to be able to name them whatever you want, depending on the context. Notice how I used verbs like “show” and “hide” on a callback as simple as one that sets a boolean value. This makes code really obvious to the reader.
Working code isn’t enough: simplify your code! (Simple is better than complex.)
One typical of that is when I see the following happen in a React component: you work with a large component that has a lot of building blocks, you try to make something work, so you make props move around until you figure it out, and in the end, you see a situation like this:
Why the useState? Why the useEffect? Maybe it made sense in the previous, messy, explorative state of the code, but it is certainly not necessary now! This would look much better:
Of course, some people might come and say “don’t try to fix something that already works”, but the counter-argument is, “for situations like these, you’ll most probably break it next time you add a feature if you don’t simplify it. Your code will change, and you should always write as if it will! Commit your changes, try to simplify your code, test, and if it works, be happy that your code is more readable!
This problem can become even worse when some very complex CSS can achieve the same results as a simpler, shorter CSS code, where the complex CSS code only worked because half the lines were overwriting each other and changing the style of that component would have taken forever (and designers *love* to change designs… so it will happen sooner or later!). Simplify your code!
Don’t make complex things more complicated than they need to be (Complex is better than complicated.)
- Using the above rule, only put one component per module (not counting the styled-components). If you put more than one component in the same file and someone knows of a problem with one of the two components, how are they supposed to look for that component in your repository? If you name your module after one of the two (or more) components, then you can’t find the other one(s) easily (you could have to do a global search… imagine if your second component was always called “Wrapper!”). Having each component in its own file helps a lot when debugging! Sometimes the filename is all you can get!
- Using the above rules, if you add a unit test in its componentName.test.js file, and maybe some stories via the fantastic Storybook library in a componentName.stories.js file, and some subcomponents that you put in their own files or even maybe subfolders next to your component… you quickly end up with a lot of files. So I do myself a favor and always default export my component in its file reserved for it, and when I need my component outside of its folder, I expose it to the rest of my repository through an index.js file. So creating a component ends up leading to a folder with a tree structure like this:
│ ├── Button.js
│ ├── Button.test.js
│ ├── index.js
| ├── stories.js
| └── styles.js
where the … in the middle represents the potentially many subcomponents required to construct the Button component. (This would probably not happen with a Button, though!) I already hear some say, “why so many files for a Button? Put everything in one file except the unit tests!” and I answer, “why so many lines of code in one file for a collection of simple concepts? Isolate the concepts so I don’t have to see a very complicated bulk all at once!”
Use a 4-spaces indent to easily detect ‘callback hell’ (Flat is better than nested.)
Before I say anything, I am not getting in the eternal debate of tabs vs. spaces (I’m a tabs guy). My point is only about the width of such tabs/spaces.
‘Callback hell’, in this case, can be achieved by heavily nesting JSX tags (recall that JSX is syntactic sugar for React.createElement, so nesting JSX *is* producing callback hell!).
The simplest trick to avoid callback hell is to isolate concepts in your heavy nesting of function calls and convert it into a function that’s less nested and easily readable. And that’s exactly what you should do! Instead of cramming your whole app in one big JSX file, you isolate concepts as components, even if you re-use them only once; the ability to test them separately is a good enough reason to want to break down a big component into smaller ones.
So the question is: when is nested ‘too nested’ and should be flattened with this technique? There is no right answer to that, but using 2 spaces of width for indentation makes the discussion uselessly longer. Use 4 spaces indentation, and then it’ll become obvious!
Last note on this: un-nesting your heavily nested React components also helps avoiding code to become ‘complicated’! Most of the time you’ll have a little bit of logic associated to each part of your big component, and by isolating a part of your big component as a smaller, ‘simpler’, more re-usable component, you’ll move the bit of logic with it, and make everything more ‘readable’. Do you see how all the tips fit together? It’s as if you couldn’t do one without the other!
Stay tuned for Part II, which will describe 5 more habits I borrowed from my learning experience in python!