└── 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 | 146 | 147 | 148 | 149 | 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 ; 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 ; 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. --------------------------------------------------------------------------------