└── README.md
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Better Coding Academy Style Guide
3 |
4 |
5 | ## What is this?
6 |
7 | This is a non-exhaustive set of coding principles you are expected to follow whilst studying at Better Coding Academy.
8 |
9 | I personally follow this style guide for all production code that I write, so feel free to use it / extend from it in any of your production projects as well.
10 |
11 |
12 | ## Why use a style guide?
13 |
14 | Having a consistent coding style across your projects is one of the easiest ways to keep coding quality and interoperability high. Learning to be consistent and follow the style guide of a company (hopefully they have one) pays handsome dividends.
15 |
16 |
17 | ## Table of Contents
18 |
19 | - [General Rules](#general-rules)
20 | - [Indentation](#indentation)
21 | - [Quotes](#quotes)
22 | - [Editor](#editor)
23 | - [Formatter](#formatter)
24 | - [HTML](#html)
25 | - [Tags](#tags)
26 | - [Attributes](#attributes)
27 | - [CSS](#css)
28 | - [Selectors](#selectors)
29 | - [Declaration Blocks](#declaration-blocks)
30 | - [Declarations](#declarations)
31 | - [Miscellaneous](#miscellaneous)
32 | - [JavaScript](#javascript)
33 | - [Semicolons](#semicolons)
34 | - [Variables](#variables)
35 | - [Objects](#objects)
36 | - [Functions](#functions)
37 | - [Quotes](#quotes-1)
38 | - [Spacing](#spacing)
39 | - [Loops](#loops)
40 | - [Switch Statements](#switch-statements)
41 | - [Imports](#imports)
42 | - [DOM Operations](#dom-operations)
43 | - [Comments](#comments)
44 | - [Miscellaneous](#miscellaneous-1)
45 | - [React](#react)
46 | - [Component Types](#component-types)
47 | - [Props](#props)
48 | - [Architecture](#architecture)
49 | - [Performance](#performance)
50 |
51 | ## General Rules
52 |
53 | ### Indentation
54 |
55 | **No tabs.** Instead, tabs should be remapped to insert two spaces. Tabs are not consistently rendered across programs and operating systems, sometimes having a width of 4, and sometimes even a width of 8, which is why using spaces that appear like tabs works best.
56 |
57 | ### Quotes
58 |
59 | **Use double quotes.**
60 |
61 | **Are there situations in which using single quotes is appropriate?** Yes, absolutely. See below under the [JavaScript](#javascript) heading for more information.
62 |
63 | ### Editor
64 |
65 | It is suggested that you use VSCode for writing code.
66 |
67 | **Why?** Well, as a non-exhaustive list, VSCode:
68 |
69 | 1. Supports a multiitude of extensions;
70 | 2. Has great customisability and extensibility;
71 | 3. Supports WSL (Windows Subsystem for Linux); and
72 | 4. Is faster than Atom (which used to be my editor of choice).
73 |
74 | ### Formatter
75 |
76 | The use of [Prettier](https://prettier.io/) is **highly recommended**, as it would help automatically enforce a number of the rules presented within this document.
77 |
78 | ## HTML
79 |
80 | ### Tags
81 |
82 | All tag names must be valid HTML tags (unless otherwise supported through the use of custom HTML elements).
83 |
84 | ```html
85 |
86 |
Hello
87 | I am a paragraph
88 |
89 |
90 | Yo
91 | ```
92 |
93 | All tag names must strictly be lowercase.
94 |
95 | ```html
96 |
97 | Hello
98 | I am a paragraph
99 |
100 |
101 | Hello
102 | I am a paragraph
103 | ```
104 |
105 | All self-closing tags must have a space and `/>` after all attributes.
106 |
107 | ```html
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | ```
117 |
118 | Non-self-closing tags must **not** self-close.
119 |
120 | ```html
121 |
122 | Hello
123 |
124 |
125 |
126 | ```
127 |
128 | ### Attributes
129 |
130 | All attributes must strictly be in alphabetical order.
131 |
132 | ```html
133 |
134 |
135 |
136 |
137 |
138 | ```
139 |
140 | All attributes must be valid HTML attributes in their valid lowercase form, or otherwise be preceded by `data-`.
141 |
142 | ```html
143 |
144 |
145 | Click Me
146 |
147 |
148 |
149 | Click Me
150 | ```
151 |
152 | ## CSS
153 |
154 | ### Selectors
155 |
156 | Don't use anything other than class names and tag names for selecting.
157 |
158 | Class name selectors must use kebab-case.
159 |
160 | **What about IDs?** IDs are reserved for selection purposes in JavaScript.
161 |
162 | **What about attributes?** They're allowed, but only in tandem with an appropriate tag name. For example:
163 |
164 | ```css
165 | /* good */
166 | input[type="text"] {
167 | font-weight: bold;
168 | }
169 |
170 | /* bad */
171 | [type="text"] {
172 | font-weight: bold;
173 | }
174 | ```
175 |
176 | The value in an attribute selector must be surrounded by double quotes.
177 |
178 | ```css
179 | /* good */
180 | input[type="text"] {
181 | font-weight: bold;
182 | }
183 |
184 | /* bad */
185 | input[type=text] {
186 | font-weight: bold;
187 | }
188 | ```
189 |
190 | ### Declaration Blocks
191 |
192 | The formatting must be as follows:
193 |
194 | ```css
195 | {
196 | /* two spaces from the left */
197 |
198 | }
199 | ```
200 |
201 | Every declaration block must be separated from other blocks (and `@import` statements) by a new line.
202 |
203 | ```css
204 | /* good */
205 | @import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");
206 |
207 | body {
208 | font-family: Roboto, sans-serif;
209 | }
210 |
211 | .body {
212 | font-weight: bold;
213 | }
214 |
215 | /* bad */
216 | @import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");
217 | body {
218 | font-family: Roboto, sans-serif;
219 | }
220 | .body {
221 | font-weight: bold;
222 | }
223 | ```
224 |
225 | Tag-based declaration blocks go first, and then class names.
226 |
227 | ```css
228 | /* good */
229 | body {
230 | font-family: Arial, Helvetica, sans-serif;
231 | }
232 |
233 | .bold {
234 | font-weight: bold;
235 | }
236 |
237 | /* bad */
238 | .bold {
239 | font-weight: bold;
240 | }
241 |
242 | body {
243 | font-family: Arial, Helvetica, sans-serif;
244 | }
245 | ```
246 |
247 | ### Declarations
248 |
249 | Declarations must be in alphabetical order.
250 |
251 | ```css
252 | /* good */
253 | .square {
254 | background-color: red;
255 | height: 100%;
256 | width: 100px;
257 | }
258 |
259 | /* bad */
260 | .square {
261 | width: 100px;
262 | height: 100%;
263 | background-color: red;
264 | }
265 | ```
266 |
267 | Where possible, try not to use vendor prefixes; instead use a CSS preprocessor to add them for you where possible. However, if you were to add vendor prefixes, add them prior to their associated property, in alphabetical order.
268 |
269 | ```css
270 | /* good */
271 | .custom-select {
272 | -moz-appearance: none;
273 | -webkit-appearance: none;
274 | appearance: none;
275 | background-color: green;
276 | -moz-border-radius: 5px;
277 | -webkit-border-radius: 5px;
278 | border-radius: 5px;
279 | }
280 | ```
281 |
282 | If you are using a vendor prefix without the "normal" version of the declaration, put it where it would belong if it did not have the vendor prefix.
283 |
284 | ```css
285 | /* good */
286 | .funky-text {
287 | -webkit-background-clip: text; /* "clip" before "image" */
288 | background-image: url(image.jpg);
289 | -webkit-text-fill-color: transparent; /* "text" after "back" */
290 | }
291 | ```
292 |
293 | ### Miscellaneous
294 |
295 | **Try not to use `!important`.** `!important` is needed in only a very small percentage of use cases. In most other cases, simply reordering your CSS declarations can do the trick. CSS declarations of the same specificity level are applied in a top-down fashion, so if you want a style to take precedence, simply try moving it further down instead of using `!important`.
296 |
297 | **Try not to use `z-index`.** Instead of using `z-index`, in the large majority of cases you can simply reorder elements. For example:
298 |
299 | ```html
300 |
305 |
306 | square 1
307 | square 2
308 | square 3
309 | square 4
310 | ```
311 |
312 | Square 4 will naturally be on top of Square 3, which will be on top of Square 2, and so on.
313 |
314 | **Try not to use inline styles.** Unless applied using JavaScript for dynamic property values (e.g. `transform`, `left`, `top`, etc.), there's probably no good reason to use inline styles.
315 |
316 | **But if I don't use inline styles, then the styles don't work!** That's a specificity issue. You probably have other styles applied at too high a specificity (e.g. using `!important`, which is a no-no as well 😉) overriding your normal styles. In this case, inline styles is like a band-aid over a stab wound - you need to dig deeper and investigate further to find the true source of the issue.
317 |
318 | ## JavaScript
319 |
320 | ### Semicolons
321 |
322 | Wherever semicolons are optional, they should be included.
323 |
324 | **Why?** Not having semicolons makes the code very trippy. For example, the following:
325 |
326 | ```js
327 | // bad
328 | function returnThree() {
329 | return
330 | 3;
331 | }
332 | ```
333 |
334 | Returns:
335 |
336 | ```js
337 | returnThree(); // undefined????
338 | ```
339 |
340 | This is because JavaScript uses a set of rules called [Automatic Semicolon Insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion) to determine whether or not it should regard that line break as the end of a statement, and it decides where it wants to place semicolons into your code. As can be seen from the example above, ASI can definitely be somewhat counter-intuitive; hence why we recommend to explicitly terminate your statements and configure your linter to catch missing semicolons.
341 |
342 | ### Variables
343 |
344 | **Never use `var`.** This ensures that you can’t reassign your references, which can lead to bugs and difficult to comprehend code.
345 |
346 | ```js
347 | // good
348 | const number = 5;
349 |
350 | // bad
351 | var number = 5;
352 | ```
353 |
354 | **Use `let` only where absolutely necessary.** Just like with `var`, this prevents unnecessary assignments.
355 |
356 | ```js
357 | // good
358 | const number = 5;
359 | let someComplexValue;
360 | if (condition) {
361 | doSomething();
362 | doSomethingElse();
363 | someComplexValue = doAnotherThing();
364 | } else {
365 | doSomethingElse();
366 | someComplexValue = doSomething();
367 | }
368 |
369 | // bad
370 | let number = 5;
371 | ```
372 |
373 | ### Objects
374 |
375 | Objects must have a curly brace on the first line, and either all properties on the same line, or one property on each line after the curly brace.
376 |
377 | ```js
378 | // good
379 | const factory = { name: "Willy Wonka" };
380 | const person = {
381 | age: "unknown",
382 | name: "Willy Wonka",
383 | occupation: "chocolatier"
384 | };
385 |
386 | // bad
387 | const factory =
388 | {
389 | name: "Wonka Willy"
390 | };
391 | const person = {
392 | age: "unknown", name: "Wonka Willy",
393 | occupation: "choco-late more like vanilla-early"
394 | };
395 | ```
396 |
397 | All object properties must be in alphabetical order.
398 |
399 | ```js
400 | // good
401 | const bill = {
402 | age: 25,
403 | email: "bill@example.com",
404 | name: "Bill"
405 | }
406 |
407 | // bad
408 | const bill = {
409 | name: "Bill",
410 | age: 25,
411 | email: "bill@example.com"
412 | };
413 | ```
414 |
415 | **Why?** It doesn't make much of a difference for smaller objects; however, when you end up with more properties, such as in the following example:
416 |
417 | ```js
418 | const options = {
419 | autoFocus: true,
420 | disabled: isSubmitting && isValid,
421 | displaySize: "lg",
422 | id: generateId("email"),
423 | name: "email",
424 | placeholder: "e.g. lucas@example.com",
425 | type: "text"
426 | };
427 | ```
428 |
429 | Assuming you know your alphabet, locating a property is simply a matter of binary search, i.e. O(log n). However, if we were to let the programmer do "whatever order feels right for them at the time", then we would get something like:
430 |
431 | ```js
432 | const options = {
433 | id: generateId("email"),
434 | name: "email",
435 | displaySize: "lg",
436 | type: "text",
437 | placeholder: "e.g. lucas@example.com",
438 | autoFocus: true,
439 | disabled: isSubmitting && isValid
440 | };
441 | ```
442 |
443 | As it becomes harder to read, some programmers would then start adding pointless new lines, grouping once again "as they see fit":
444 |
445 | ```js
446 | const options = {
447 | // no one
448 | id: generateId("email"),
449 | name: "email",
450 |
451 | // has any idea
452 | displaySize: "lg",
453 | type: "text",
454 |
455 | // why these are grouped like this
456 | placeholder: "e.g. lucas@example.com",
457 | autoFocus: true,
458 | disabled: isSubmitting && isValid
459 | };
460 | ```
461 |
462 | So yeah, no good. Finding a property in an unordered object is O(n), an order of magnitude worse than O(log n).
463 |
464 | ### Functions
465 |
466 | Use arrow functions where possible. Use the `function` keyword only when you need the `this` value.
467 |
468 | **Why?** Arrow functions are shorter and give a more intuitive understanding of `this`. `this` should be reserved for use in specific situations instead of having every function have its own "accidental" `this` value.
469 |
470 | ```js
471 | // good
472 | const add = (num1, num2) => num1 + num2;
473 |
474 | const handleSubmit = async ({ name }) => {
475 | const result = await addUser({ variables: { name }});
476 | return result.data;
477 | }
478 |
479 | input.addEventListener("keydown", function (evt) {
480 | evt.preventDefault();
481 |
482 | alert(`Hello ${this.value}!`);
483 | });
484 |
485 | // bad
486 | function add(num1, num2) {
487 | return num1 + num2;
488 | }
489 |
490 | async function handleSubmit({ name }) {
491 | const result = await addUser({ variables: { name }});
492 | return result.data;
493 | }
494 | ```
495 |
496 | When definining the parameters of a function/method, **one object parameter is generally preferable to multiple arguments**.
497 |
498 | For example, instead of this:
499 |
500 | ```js
501 | // bad
502 | class User {
503 | constructor(id, name, email, address) {
504 | this.id = id;
505 | this.name = name;
506 | this.email = email;
507 | this.address = address;
508 | }
509 | }
510 | ```
511 |
512 | Do this:
513 |
514 | ```js
515 | // good
516 | class User {
517 | constructor({ address, email, id, name }) {
518 | this.id = id;
519 | this.name = name;
520 | this.email = email;
521 | this.address = address;
522 | }
523 | }
524 | ```
525 |
526 | This has three main benefits:
527 |
528 | 1. **It easily allows for properties to be optional.** Imagine if `email` were to be optional; creating an object using the first syntax would look like:
529 |
530 | ```js
531 | new User(1, "Lucas", undefined, "1 Main St.");
532 | ```
533 |
534 | However, for the second one it would simply be:
535 |
536 | ```js
537 | new User({
538 | address: "1 Main St.",
539 | // notice how we leave out email entirely
540 | id: 1,
541 | name: "Lucas"
542 | });
543 | ```
544 |
545 | 2. **It removes the ambiguity of ordering.** In the first example, there is no real reason to use the order `id, name, email, address` as opposed to, say, `id, name, address, email`, or any other combination really. When using an object and destructuring it, it is easier than remembering an arbitrary order.
546 | 3. **It gives each "argument" a name.** Obviously in the second example we only have one argument (an object); however, each one of those properties has a name, which adds incredible semantics into your function. Look at the difference between these two:
547 |
548 | ```php
549 | // actual PHP code... yikes
550 | $canvas = imagecreatetruecolor(200, 200);
551 |
552 | // yikes
553 | $pink = imagecolorallocate($canvas, 255, 105, 180);
554 | $white = imagecolorallocate($canvas, 255, 255, 255);
555 | $green = imagecolorallocate($canvas, 132, 135, 28);
556 |
557 | // yikes
558 | imagerectangle($canvas, 50, 50, 150, 150, $pink);
559 | imagerectangle($canvas, 45, 60, 120, 100, $white);
560 | imagerectangle($canvas, 100, 120, 75, 160, $green);
561 | ```
562 |
563 | Using what we've just learned, what if we rewrite this a little?
564 |
565 | ```js
566 | const canvas = imageCreateTrueColor({ height: 200, width: 200 });
567 |
568 | const pink = imageColorAllocate({
569 | blue: 180,
570 | canvas,
571 | green: 105,
572 | red: 255
573 | });
574 | const white = imageColorAllocate({
575 | blue: 255,
576 | canvas,
577 | green: 255,
578 | red: 255
579 | });
580 | const green = imageColorAllocate({
581 | blue: 28,
582 | canvas,
583 | green: 135,
584 | red: 132
585 | });
586 |
587 | imageRectangle({
588 | canvas,
589 | color: pink,
590 | x1: 50,
591 | x2: 150,
592 | y1: 50,
593 | y2: 150
594 | });
595 | imageRectangle({
596 | canvas,
597 | color: white,
598 | x1: 45,
599 | x2: 120,
600 | y1: 60,
601 | y2: 100
602 | });
603 | imageRectangle({
604 | canvas,
605 | color: green,
606 | x1: 100,
607 | x2: 75,
608 | y1: 120,
609 | y2: 160
610 | });
611 | ```
612 |
613 | Yes, it is longer; however, look at all the additional information that is now available. No need to remember the order of parameters, and that is a huge deal. It might take 20% longer to type the first time round, but each time a change is made, the reduction in ambiguity will save much more than the original 20% cost.
614 |
615 | **Do not mutate function arguments.** Instead, if the argument is a primitive, consider storing its value in a new object; if the argument is an object (including arrays), consider spreading it or otherwise creating a new copy.
616 |
617 | ```js
618 | // bad
619 | const magickFruits = numFruits => {
620 | numFruits *= 2;
621 |
622 | // etc.
623 | }
624 |
625 | // good
626 | const magickFruits = numFruits => {
627 | let newNumFruits = numFruits * 2;
628 |
629 | // etc.
630 | }
631 | ```
632 |
633 | **Why?** If the argument is a primitive, once you change its value, you no longer have access to the original value within the scope of the function. Any code that you write later on in the function that might rely on the argument will now no longer be able to access the original value. If the argument is an object, you are mutating its original value, which introduces side effects into the rest of your code - and is even worse. For example:
634 |
635 | ```js
636 | // bad
637 | const append1 = array => {
638 | array.push(1);
639 | return [...array];
640 | }
641 |
642 | const arr = [1, 2, 3, 4, 5];
643 | const newArr = append1(arr);
644 |
645 | arr; // [1, 2, 3, 4, 5, 6]
646 | newArr; // [1, 2, 3, 4, 5, 6]
647 |
648 | // good
649 | const append1 = array => {
650 | const newArray = [...array, 1];
651 | return newArray;
652 | }
653 |
654 | const arr = [1, 2, 3, 4, 5];
655 | const newArr = append1(arr);
656 |
657 | arr; // [1, 2, 3, 4, 5]
658 | newArr; // [1, 2, 3, 4, 5, 6]
659 | ```
660 |
661 | ### Quotes
662 |
663 | Use double quotes `""` for strings.
664 |
665 | ```js
666 | // good
667 | const message = "Hello!";
668 |
669 | // bad
670 | const message = 'Hello!';
671 | ```
672 |
673 | However, if you wish to embed double quotes in your string, you can use single quotes or backticks - avoid escaping where possible.
674 |
675 | ```js
676 | // good
677 | const message = 'And then he said "Hello", like a maniac!';
678 | const message = `And then he said "Hello", like a maniac!`;
679 |
680 | // bad
681 | const message = "And then he said \"Hello\", like a maniac!";
682 | ```
683 |
684 | Use backticks whenever you need to interpolate a value into your string.
685 |
686 | ```js
687 | // bad
688 | console.log("How are you, " + name + "?");
689 | console.log(["How are you, ", name, "?"].join());
690 |
691 | // good
692 | console.log(`How are you, ${name}?`);
693 | ```
694 |
695 | ### Spacing
696 |
697 | Put one new line before and after every function in your code (unless there's no code before/after it).
698 |
699 | ```js
700 | // good
701 | function func1() {
702 | // something
703 | }
704 |
705 | function func2() {
706 | // something else
707 | }
708 |
709 | // bad
710 | function func1() {
711 | // something
712 | }
713 | function func2() {
714 | // something else
715 | }
716 | ```
717 |
718 | Put one new line before and after every method and property in your class (unless it's at the start/end of your class).
719 |
720 | ```js
721 | // good
722 | class Form extends Component {
723 | state = { a: 1, b: 2 };
724 |
725 | handleChange = () => {
726 | // ...
727 | }
728 |
729 | render() {
730 | // ...
731 | }
732 | }
733 |
734 | // bad
735 | class Form extends Component {
736 | state = { a: 1, b: 2 };
737 | handleChange = () => {
738 | // ...
739 | }
740 | render() {
741 | // ...
742 | }
743 | }
744 | ```
745 |
746 | ### Loops
747 |
748 | Try not to use `for` loops where possible. Instead, consider using Array iteration methods, e.g. `Array.prototype.forEach` or `Array.prototype.map`.
749 |
750 | **Why?** Specificity and clarity. When you see a `.map` method, assuming no crazy stuff is going on, you know that a new array is being generated from an existing array. When you see a `.forEach` method, you know that it is iterating thorugh properties, and so on and so forth for `.reduce`, `.some`, `.every`, etc. - every single method is clearly defined for a particular purpose.
751 |
752 | However when you see a `for` loop, there is no guarantee for what is going to happen, and in many cases, when it tries to do more than one thing, the loop quickly becomes overcomplicated and poorly designed.
753 |
754 | ```js
755 | const names = ["Aaron", "Amanda", "Arthur"];
756 |
757 | // good
758 | names.forEach(name => {
759 | console.log(name);
760 | })
761 |
762 | const namesInCaps = names.map(name => name.toUpperCase());
763 |
764 | // bad
765 | for (let i = 0; i < names.length; i++) {
766 | console.log(name);
767 | }
768 | ```
769 |
770 | However, there are some cases in which you will need to use `for` loops. In such cases, you should use a proper counter name (not `i`) where possible. However, using a generic name like `i` is okay if you are only repeating something a certain number of times, and do not plan to use the index for any purposes.
771 |
772 | ### Switch Statements
773 |
774 | Try to use `switch` statements as little as possible.
775 |
776 | **Why?** They're essentially `if` statements that sacrifice flexibility for a little bit more minimalism (and even that is debatable).
777 |
778 | For example, look at the following code:
779 |
780 | ```js
781 | const status = "GO";
782 | switch (status) {
783 | case "GO":
784 | console.log("Let's go!");
785 | break;
786 | case "STOP":
787 | console.log("Time to stop!");
788 | break;
789 | }
790 | ```
791 |
792 | And its equivalent using `if` statements:
793 |
794 | ```js
795 | const status = "GO";
796 | if (status === "GO") {
797 | console.log("Let's go!");
798 | } else if (status === "STOP") {
799 | console.log("Time to stop!");
800 | }
801 | ```
802 |
803 | I mean... it's 3 lines shorter - I guess the only thing is that `status` is not repeated. However, going back to the `switch` statement, what if you want to have some more granular control?
804 |
805 | ```js
806 | const status = "GO";
807 | const car = "RACECAR";
808 | switch (status) {
809 | case "GO":
810 | console.log("Let's go!");
811 | break;
812 | case "STOP":
813 | // yikes
814 | if (car === "RACECAR") {
815 | console.log("Let's GO!");
816 | } else {
817 | console.log("Time to stop!");
818 | }
819 | break;
820 | }
821 | ```
822 |
823 | Compared to if we were using `if` statements:
824 |
825 | ```js
826 | const status = "GO";
827 | const car = "RACECAR";
828 | if (status === "GO") {
829 | console.log("Let's go!");
830 | } else if (status === "STOP" && car === "RACECAR") {
831 | console.log("Let's GO!");
832 | } else if (status === "STOP") {
833 | console.log("Time to stop!");
834 | }
835 | ```
836 |
837 | **Are there cases in which using a switch statement is appropriate?** Sure! Well, maybe... I don't know. At the lowest level it's a matter of personal preference; however, hopefully you can see why I do not advocate its widespread use.
838 |
839 | ### Imports
840 |
841 | There is a strict order that you should follow for all of your imports. Refer to the following:
842 |
843 | ```js
844 | import { rgba } from "polished";
845 | import React from "react";
846 | import { Route, Switch } from "react-router-dom";
847 | import styled from "styled-components";
848 |
849 | import TestFramework from "#test/TestFramework";
850 | import ContentTitle from "$shared/components/layout/ContentTitleSC";
851 | import InlineHorizontalNavigation, { Item } from "$shared/components/layout/InlineHorizontalNavigation";
852 | import ResponsiveContainer from "$shared/components/layout/ResponsiveContainerSC";
853 | import BasicLoader from "$shared/components/loaders/BasicLoader";
854 |
855 | import validateBusiness from "./_validateBusiness";
856 | import PersonInformation from "./PersonInformation";
857 | import SubmitAction from "./SubmitAction";
858 | ```
859 |
860 | Let's talk about the rules that are being enforced here:
861 |
862 | 1. **There are a maximum of three import groups**. The first group of imports are **global**, the second are **aliased**, and the third are **relative**. Global imports are sourced from `node_modules`, aliased imports are aliased to a local directory, and relative imports are imported relative to the current file (notice how they start with `./`).
863 | 2. **In each import group, imports are ordered alphabetically**. Let's first look at the top import group:
864 |
865 | ```js
866 | import { rgba } from "polished";
867 | import React from "react";
868 | import { Route, Switch } from "react-router-dom";
869 | import styled from "styled-components";
870 | ```
871 |
872 | Notice how `polished` is first (`polished` starts with `p`), and then `react`, then `react-router-dom`, then `styled-components`. Let's now look at the second group:
873 |
874 | ```js
875 | import TestFramework from "#test/TestFramework";
876 | import ContentTitle from "$shared/components/layout/ContentTitleSC";
877 | import InlineHorizontalNavigation, { Item } from "$shared/components/layout/InlineHorizontalNavigation";
878 | import ResponsiveContainer from "$shared/components/layout/ResponsiveContainerSC";
879 | import BasicLoader from "$shared/components/loaders/BasicLoader";
880 | ```
881 |
882 | Notice how `#test/TestFramework` comes first - the first character is a `#`, which comes before `$` (`#` has a character code of 35, `$` has a character code of 36). This is the same alphabetical principle we use for the first section as well. Let's now look at the last section:
883 |
884 | ```js
885 | import validateBusiness from "./_validateBusiness";
886 | import PersonInformation from "./PersonInformation";
887 | import SubmitAction from "./SubmitAction";
888 | ```
889 |
890 | `./_validateBusiness` comes first because of the underscore.
891 |
892 | ### DOM Operations
893 |
894 | Try to update the DOM as little as possible.
895 |
896 | **Why?** Updating the DOM is "slow". Slow, as in relatively slow compared to updating internal JavaScript variables. In other words:
897 |
898 | ```js
899 | someHTMLElement.innerText = "Hello"; // this
900 | someVariable = "Hello"; // is slower than this
901 | ```
902 |
903 | Compare the following two bits of code that do essentially the same thing:
904 |
905 | ```js
906 | const preTag = document.createElement("pre");
907 | document.body.appendChild(preTag);
908 |
909 | // bad
910 | for (let i = 0; i < 100; i++) {
911 | preTag.innerText += `${Math.random()}\n`; // in total, 100 DOM update operations
912 | }
913 |
914 | // good
915 | let preTagContents = preTag.innerText; // store the value (DOM access operation)
916 | for (let i = 0; i < 100; i++) {
917 | preTagContents += `${Math.random()}\n`; // update the variable - much faster...
918 | }
919 | preTag.innerText = preTagContents; // and write it into the DOM! (a single DOM update operation)
920 | ```
921 |
922 | [Check out this JSPerf test to run both of these snippets on your own computer.](https://jsperf.com/dom-access-speed/1)
923 |
924 | **Do not read style properties to determine information about your page.** Take a look at the following code, designed to increase the font size of an element by 1px:
925 |
926 | ```js
927 | // headingEl already exists
928 |
929 | // bad
930 | headingEl.style.fontSize = `${parseInt(headingEl.style.fontSize) + 1}px`;
931 | ```
932 |
933 | **Why?** Disregarding whether this works or not (and in some cases it won't work) this is an anti-pattern because we're depending on data that is stored within the DOM.
934 |
935 | Instead, do something like:
936 |
937 | ```js
938 | // headingEl already exists
939 |
940 | const DEFAULT_HEADING_FONT_SIZE = 16;
941 | let headingFontSize = DEFAULT_HEADING_FONT_SIZE; // pre-set this
942 |
943 | // good
944 | headingEl.style.fontSize = `${++headingFontSize}px`; // adds 1 and then inlines it
945 | ```
946 |
947 | This way our `headingFontSize` variable (i.e. our JavaScript) is the source of truth for the data - much cleaner, no `parseInt` required.
948 |
949 | **Do not store data on the DOM.** Instead, store your data inside your JavaScript code, whether that is in the form of single variables, objects, arrays, etc.
950 |
951 | **Why?** The DOM the presentational layer, not the data layer. Storing data on the DOM only to be accessed by your own JavaScript code results in a new **source of truth** (i.e. the DOM, where the data is stored). This only leads to issues further down the track.
952 |
953 | ```js
954 | /* bad */
955 | someElement.isValid = false;
956 |
957 | // later on in the code...
958 | if (!someElement.isValid) {
959 | // ...
960 | }
961 |
962 | /* good */
963 | let isSomeElementValid = false;
964 |
965 | // later on in the code
966 | if (!isSomeElementValid) {
967 | // ...
968 | }
969 | ```
970 |
971 | ### Comments
972 |
973 | **Don't comment anything that is already obvious in your code.**
974 |
975 | The following is an example of how **NOT** to comment:
976 |
977 | ```js
978 | // Hook
979 | function useLocalStorage(key, initialValue) {
980 | // State to store our value
981 | // Pass initial state function to useState so logic is only executed once
982 | const [storedValue, setStoredValue] = useState(() => {
983 | try {
984 | // Get from local storage by key
985 | const item = window.localStorage.getItem(key);
986 | // Parse stored json or if none return initialValue
987 | return item ? JSON.parse(item) : initialValue;
988 | } catch (error) {
989 | // If error also return initialValue
990 | console.log(error);
991 | return initialValue;
992 | }
993 | });
994 | ```
995 |
996 | None of the comments shown above provide any additional information to the given code - all of that information can be inferred from the code. In comaprison, the following is an example of a good comment:
997 |
998 | ```js
999 | module.exports = {
1000 | env: {
1001 | browser: true,
1002 | es6: true, // gets Promise working
1003 | jest: true,
1004 | node: true
1005 | },
1006 | ```
1007 |
1008 | In comparison, the one comment here for `es6: true` tells us what we otherwise could not have inferred. This above snippet is from a `.eslintrc.js` file, and `es6: true` does more than just get `Promise` working - however, we're writing this comment to explain why this is being done in this particular case; that is, to get `Promise` working.
1009 |
1010 | ### Miscellaneous
1011 |
1012 | **Avoid magic numbers.** Magic numbers are unique values with unexplained meaning or multiple occurrences which could (preferably) be replaced with one or more named constants.
1013 |
1014 | Let's look at the following example:
1015 |
1016 | ```js
1017 | const square = document.createElement("div");
1018 | square.style.width = "100px";
1019 | square.style.height = "100px";
1020 | square.style.position = "absolute";
1021 | square.style.left = "100px";
1022 | square.style.top = "100px";
1023 | ```
1024 |
1025 | In the above, the `100px` shown on the four lines are the magic numbers. Specifically, they refer to values whose origins are unclear, and are not clearly explained.
1026 |
1027 | You might not be able to see why the above example is bad; however, what if we continued using this `square`?
1028 |
1029 | ```js
1030 | // some sort of collision detection...
1031 | const squareRect = square.getBoundingClientRect();
1032 | if (
1033 | squareRect.left <= collisionRect.left &&
1034 | squareRect.left + 100 >= collisionRect.left &&
1035 | squareRect.top <= collisionRect.top &&
1036 | squareRect.top + 100 >= collisionRect.top
1037 | ) {
1038 | // etc...
1039 | }
1040 | ```
1041 |
1042 | All of a sudden the number `100` starts appearing all over your code, and for no real reason! Changing it in any location will break something, but the longer you leave it there, the less clear its purpose becomes.
1043 |
1044 | Instead, stem it from the beginning with a variable:
1045 |
1046 | ```js
1047 | const SQUARE_SIZE = 100;
1048 | const SQUARE_DEFAULT_LEFT = 100;
1049 | const SQUARE_DEFAULT_TOP = 100;
1050 |
1051 | const square = document.createElement("div");
1052 | square.style.width = `${SQUARE_SIZE}px`;
1053 | square.style.height = `${SQUARE_SIZE}px`;
1054 | square.style.position = "absolute";
1055 | square.style.left = `${SQUARE_DEFAULT_LEFT}px`;
1056 | square.style.top = `${SQUARE_DEFAULT_TOP}px`;
1057 | ```
1058 |
1059 | Notice the UPPER_SNAKE_CASING that we use for these constants. Because they're values that are pre-defined by the programmer and hence cannot be customised, we use this casing.
1060 |
1061 | Notice how we used a different constant for `left` and for `top`. This is to prevent confusion as to why the left and top values are being set to the square size. If you wish the left and top values to be related to the square size, you could do:
1062 |
1063 | ```js
1064 | const SQUARE_SIZE = 100;
1065 | const SQUARE_DEFAULT_LEFT = SQUARE_SIZE;
1066 | const SQUARE_DEFAULT_TOP = SQUARE_SIZE;
1067 | ```
1068 |
1069 | This is a good compromise to semantically show that `SQUARE_DEFAULT_LEFT` and `SQUARE_DEFAULT_TOP` are both based off `SQUARE_SIZE`.
1070 |
1071 | ## React
1072 |
1073 | ### Component Types
1074 |
1075 | **Use function-based components wherever possible, and class-based components only when absolutely necessary.** "Absolutely necessary" includes, as a very limited list:
1076 |
1077 | 1. [Error boundaries that use `componentDidCatch`](https://reactjs.org/docs/error-boundaries.html); and
1078 | 2. Components that **need** a class-based structure to work properly / be properly optimised. Expanding upon this, this does **not** mean that you get to arbitrarily decide when to use class-based components based on a "gut feeling" - you **must** look at hard data for evidence that using a class-based component provides a statistically significant optimisation benefit. See [Performance](#performance) below.
1079 |
1080 | ### Props
1081 |
1082 | **All props should be in alphabetical order.**
1083 |
1084 | ```jsx
1085 | // good
1086 |
1087 |
1088 | // bad
1089 |
1090 | ```
1091 |
1092 | **Event handler props should be named in the convention of `onAction`.**
1093 |
1094 | ```jsx
1095 | // good
1096 | {
1098 | // ...
1099 | }}
1100 | onUpdate={() => {
1101 | // ...
1102 | }}
1103 | />
1104 |
1105 | // bad
1106 | {
1108 | // ...
1109 | }}
1110 | updated={() => {
1111 | // ...
1112 | }}
1113 | />
1114 | ```
1115 |
1116 | **Why?** React does this with all of its built-in events. It is important to discern between normal props and event handler props, so we might as well follow React's convention.
1117 |
1118 | **When accessing event handler props inside your component, they should be remapped to use the word "push".**
1119 |
1120 | ```jsx
1121 | // good
1122 | const CreateAccount = ({ onCreate: pushCreate, onUpdate: pushUpdate }) => {
1123 | // later on...
1124 | pushCreate({ ... });
1125 | }
1126 |
1127 | // bad
1128 | const CreateAccount = ({ onCreate, onUpdate }) => {
1129 | // later on...
1130 | onCreate({ ... });
1131 | }
1132 | ```
1133 |
1134 | **Why?** A function called `onEventName` doesn't make much sense - `pushEventName` makes a lot more sense.
1135 |
1136 | ### Architecture
1137 |
1138 | **When deciding between hooks, render props and higher-order components, always go with hooks wherever possible.**
1139 |
1140 | ```jsx
1141 | // #1
1142 | const MyComponent = () => {
1143 | const mousePosition = useMouse();
1144 |
1145 | // mousePosition.x, mousePosition.y
1146 | }
1147 |
1148 | // #2
1149 | const MyComponent = () => {
1150 | return (
1151 |
1152 | {({ x, y }) => {
1153 | // ...
1154 | }}
1155 |
1156 | )
1157 | }
1158 |
1159 | // #3
1160 | const MyComponent = ({ x, y }) => {
1161 | // ...
1162 | }
1163 |
1164 | export default withMouse(MyComponent);
1165 | ```
1166 |
1167 | **Why?** Well, let's start with higher-order components.
1168 |
1169 | Higher order components are bad for two main reasons:
1170 |
1171 | 1. **They take up a fixed prop name, possibly removing other props.** For example, imagine for above example #3 we want to include an `x` and `y` prop on the component:
1172 |
1173 | ```jsx
1174 |
1175 | ```
1176 |
1177 | Both of these values will be overwritten by the values coming from the higher order component. This issue can also arise when you wish to use multiple higher order components:
1178 |
1179 | ```jsx
1180 | export default withMouse(withPage(MyComponent)); // if withMouse and withPage set the same props, there will be clashing issues
1181 | ```
1182 |
1183 | 2. **They do not clearly identify the source of your data.** `withMouse(MyComponent)` does not tell you which props are being included onto the component (if any), hence increasing the amount of time spent debugging and fixing up the code.
1184 |
1185 | Okay then, now let's look at **render props**. Because render props give you data within a function parameter, you can freely rename it however you like. For example:
1186 |
1187 | ```jsx
1188 |
1189 | {({ x, y }) => (
1190 |
1191 | {({ x: pageX, y: pageY }) => {
1192 | // ^ big brain
1193 | }}
1194 |
1195 | )}
1196 |
1197 | ```
1198 |
1199 | However, render props still have their own issues:
1200 |
1201 | 1. **They don't allow you to use their data outside of the `return` statement.** With the example above, you can't use the `x` and `y` values in any state variables, `useEffect` hooks, or any other functions within your component, because it's only accessible within the `return` statement.
1202 | 2. **They get nested... really quickly.** Imagine we have three render prop components within a given component:
1203 |
1204 | ```jsx
1205 | const MyComponent = () => {
1206 | return (
1207 |
1208 | {({ x, y }) => (
1209 |
1210 | {({ x: pageX, y: pageY }) => (
1211 |
1212 | {({ api }) => {
1213 | // yikes
1214 | }}
1215 |
1216 | )}
1217 |
1218 | )}
1219 |
1220 | )
1221 | };
1222 | ```
1223 |
1224 | So now, onto the final (and best) solution!
1225 |
1226 | Hooks address all of the above issues.
1227 |
1228 | 1. **Hooks don't have any fixed prop names** - you can rename however you like:
1229 |
1230 | ```jsx
1231 | const { x, y } = useMouse();
1232 | const { x: pageX, y: pageY } = usePage();
1233 | ```
1234 |
1235 | 2. **Hooks clearly identify the source of the data** - in the example above, it's clear that `x` and `y` come from `useMouse`, and `pageX` and `pageY` come from `usePage`.
1236 | 3. **Hooks allow you to access data outside of the `return` statement.** For example, you can do stuff like:
1237 |
1238 | ```jsx
1239 | const { x: pageX, y: pageY } = usePage();
1240 |
1241 | useEffect(() => {
1242 | // this runs whenever pageX or pageY changes
1243 | }, [pageX, pageY]);
1244 | ```
1245 |
1246 | 4. **Hooks don't get nested at all.** With the above render prop monstrosity rewritten using hooks, the code would look something like:
1247 |
1248 | ```jsx
1249 | const { x, y } = useMouse();
1250 | const { x: pageX, y: pageY } = usePage();
1251 | const { api } = useConnection();
1252 | ```
1253 |
1254 | Three lines of beautiful code.
1255 |
1256 | ## Performance
1257 |
1258 | > “Premature optimization is the root of all evil." – Donald Knuth
1259 |
1260 | What does this mean? Well, imagine you have a section of code like this:
1261 |
1262 | ```jsx
1263 | // good, not bad... but read on
1264 | const MyComponent = () => {
1265 | useSomeHook();
1266 |
1267 | const handleClick = () => {
1268 | alert("hello!");
1269 | }
1270 |
1271 | return Click Me ;
1272 | };
1273 | ```
1274 |
1275 | You render this component onto your page, and everything is working fine.
1276 |
1277 | However, you recently read an article published by an internet stranger, saying that this type of code is bad as `handleClick` gets re-defined every time `MyComponent` is run, which is bad for performance.
1278 |
1279 | So, to get around this issue you decide to refactor this into a class-based component:
1280 |
1281 | ```jsx
1282 | // uh...
1283 | class MyComponent extends Component {
1284 | handleClick = () => {
1285 | alert("hello!");
1286 | }
1287 |
1288 | render() {
1289 | useSomeHook(); // uh oh...
1290 | return Click Me ;
1291 | }
1292 | }
1293 | ```
1294 |
1295 | OH wait... `useSomeHook` doesn't work anymore - we need to use functional components for that to work! Guess we'll have to rewrite that hook as well.
1296 |
1297 | So now, because you tried to prematurely optimise `MyComponent`, not only have you introduced a class-based component, but you've also introduced a second, non-hook-based copy of `useSomeHook`, just so that you can get "performance benefits". However, instead of getting "performance benefits", you simply reduced your own efficiency as a developer.
1298 |
1299 | **What happened?** Well, you didn't ask yourself the following questions:
1300 |
1301 | 1. **Is my code slow?** Yep. Is my code actually slow? Not like, "this seems bad so I'll change it" slow - is there a noticeable performance hit on my page?
1302 | 2. **Is this section of code causing the performance hit?** In some cases it might be other sections of code. Unless you've done your research, it's very hard to tell. In the above example, we did not collect evidence showing that the `handleClick` method of `MyComponent` was causing a noticeable performance hit.
1303 | 3. **Are the changes I'm proposing going to fix or mitigate the performance issue?** In the above example, we did not collect evidence showing that the use of a class-based component will noticeably increase the speed of `MyComponent` compared to a functional component.
1304 |
1305 | Before you optimise yourself down a rabbit hole, always ask yourself the above questions, and make sure that you have good reasons to perform the optimisations you are about to do.
1306 |
1307 | **So, does that mean we should never optimise?** No, of course not! Optimisation is very important; however, optimisation without data is just over-complication, and that is certainly not a good thing.
--------------------------------------------------------------------------------