50 | >
51 | );
52 | }
53 |
54 | export default Features;
55 |
--------------------------------------------------------------------------------
/components/StackBlitz.tsx:
--------------------------------------------------------------------------------
1 | export default function StackBlitz(props: {
2 | name: string;
3 | height?: number;
4 | view?: "preview" | "editor",
5 | openFile?: string;
6 | hideExplorer?: boolean;
7 | showDevtools?: boolean;
8 | }) {
9 |
10 | return (
11 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/custom.css:
--------------------------------------------------------------------------------
1 | .nextra-body.full {
2 | margin: 0 auto;
3 | }
4 |
5 | code {
6 | @apply text-sm;
7 | }
8 |
9 | .dark .invert-on-dark {
10 | filter: invert(1) brightness(1.8);
11 | }
12 |
13 | .nextra-toc-meta {
14 | display: none;
15 | }
16 |
17 | code {
18 | font-family: "Menlo", PT Mono,Consolas,Monaco,"Andale Mono","Ubuntu Mono",monospace !important;
19 | }
20 |
21 | video {
22 | /* border: 2px solid #262626;
23 | border-radius: 10px; */
24 | width: 100%;
25 | margin: 2em 0;
26 | /* padding: 0 2em;
27 | background: black; */
28 | }
29 |
30 | .dark p, .dark ol, .dark li {
31 | color: #e3e3e3;
32 | }
33 |
34 | iframe {
35 | border-radius: 7px;
36 | margin: 2em 0;
37 | }
38 |
39 | p > strong {
40 | color: #FF42A1;
41 | }
42 |
43 | .nextra-sidebar-menu {
44 | box-shadow: none !important;
45 | }
46 |
47 | .dark .nextra-sidebar-menu > div {
48 | background: black !important;
49 | }
50 |
51 | .light .nextra-sidebar-menu > div {
52 | background: white !important;
53 | }
54 |
55 | a > h1 {
56 | font-weight: 500;
57 | }
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withNextra = require("nextra")({
2 | theme: "nextra-theme-docs",
3 | themeConfig: "./theme.config.js",
4 | unstable_flexsearch: true,
5 | unstable_staticImage: true,
6 | });
7 |
8 | module.exports = withNextra({
9 | // reactStrictMode: true,
10 | experiments: {
11 | esmExternals: true,
12 | },
13 | async redirects() {
14 | return [
15 | {
16 | source: '/',
17 | destination: '/patterns',
18 | permanent: true,
19 | },
20 | ]
21 | },
22 | });
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript-react-patterns",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next",
7 | "start": "next start",
8 | "build": "next build ",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@heroicons/react": "1.0.5",
13 | "@react-aria/ssr": "3.1.2",
14 | "next": "12.1.0",
15 | "nextra": "2.0.0-alpha.50",
16 | "nextra-theme-docs": "2.0.0-alpha.53",
17 | "react": "17.0.2",
18 | "react-dom": "17.0.2"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^18.7.13",
22 | "@types/react": "17.0.37",
23 | "autoprefixer": "10.2.6",
24 | "postcss": "8.3.5",
25 | "tailwindcss": "3.0.15",
26 | "typescript": "^4.8.2"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pages/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/pages/.DS_Store
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles.css";
2 | import "nextra-theme-docs/style.css";
3 | import "../custom.css";
4 | import Head from "next/head";
5 | import { SSRProvider } from "@react-aria/ssr";
6 |
7 | // Shim requestIdleCallback in Safari
8 | if (typeof window !== "undefined" && !("requestIdleCallback" in window)) {
9 | window.requestIdleCallback = (fn) => setTimeout(fn, 1);
10 | window.cancelIdleCallback = (e) => clearTimeout(e);
11 | }
12 |
13 | export default function Nextra({ Component, pageProps }) {
14 | const getLayout = Component.getLayout || ((page) => page);
15 |
16 | return getLayout(
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/google-font-display */
2 |
3 | import Document, { Html, Head, Main, NextScript } from "next/document";
4 |
5 | class MyDocument extends Document {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
16 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default MyDocument;
35 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 | import React from "react";
3 |
4 | export default function Index() {
5 | const router = useRouter();
6 |
7 | React.useEffect(() => {
8 | router.push("/patterns");
9 | }, []);
10 |
11 | return null;
12 | }
13 |
--------------------------------------------------------------------------------
/pages/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "*": {
3 | "type": "page"
4 | },
5 | "index": {
6 | "title": "Home",
7 | "hidden": true,
8 | "theme": {
9 | "layout": "raw"
10 | }
11 | },
12 | "patterns": {
13 | "title": "All Patterns"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/factory-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Factory Pattern
5 |
6 | Use a factory function in order to create objects
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | With the Factory Pattern, we can use a special function - the factory function - to create many of the same objects.
13 |
14 |
26 |
27 | ---
28 |
29 | ### Implementation
30 |
31 | A factory function can be any function that returns an object.
32 |
33 | ```js
34 | const createUser = (firstName, lastName) => ({
35 | id: crypto.randomUUID(),
36 | createdAt: Date.now(),
37 | firstName,
38 | lastName,
39 | fullName: `${firstName} ${lastName}`,
40 | });
41 |
42 | createUser("John", "Doe");
43 | createUser("Sarah", "Doe");
44 | createUser("Lydia", "Hallie");
45 | ```
46 |
47 | ---
48 |
49 | ### Tradeoffs
50 |
51 |
52 | DRY: The factory pattern is useful when we have to create multiple
53 | objects that share the same properties, without having to repeat the same code
54 | over and over. A factory function can easily return a custom object depending
55 | on the current environment, or user-specific configuration.
56 |
57 |
58 |
59 | Not really a pattern: In JavaScript, the factory pattern isn't much more than a function that returns an object without using the new keyword. ES6 arrow functions allow us to create small factory functions that implicitly return an object each time.
60 |
61 | However, in many cases it may be more memory efficient to create new instances instead of new objects each time.
62 |
63 | ```js
64 | class User {
65 | constructor(firstName, lastName, email) {
66 | this.firstName = firstName;
67 | this.lastName = lastName;
68 | this.email = email;
69 | }
70 |
71 | async getPosts() {
72 | const posts = await fetch(`https://my.cms.com/posts/user/${this.id}`);
73 | return posts;
74 | }
75 | }
76 |
77 | const user1 = new User({
78 | firstName: "John",
79 | lastName: "Doe",
80 | email: "john@doe.com",
81 | });
82 |
83 | const user2 = new User({
84 | firstName: "Jane",
85 | lastName: "Doe",
86 | email: "jane@doe.com",
87 | });
88 | ```
89 |
90 | The `fullName` method is the same for all the objects that were created. By creating new instances, the `fullName` method is available on the prototype instead of on the objec, which saves memory.
91 |
92 |
93 |
94 | ---
95 |
96 | ### Exercise
97 |
98 | #### Challenge
99 |
100 | Refactor the following code to use a `createBook` factory function.
101 |
102 |
103 |
104 | #### Solution
105 |
106 |
107 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/introduction.mdx:
--------------------------------------------------------------------------------
1 | # Design Patterns
2 |
3 | Design patterns are concepts to performantly solve commonly recurring problems in software architecture.
4 |
5 | Over the years, the JavaScript ecosystem and language has changed rapidly, and design patterns that used to be valuable some years ago may not be as valuable as they used to be.
6 |
7 | In this chapter, we'll cover some traditional and modern design patterns, and walk through the implementation, the tradeoffs, and how useful they still are nowadays.
8 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "introduction": "1. Introduction",
3 | "module-pattern": "2. Module Pattern",
4 | "singleton-pattern": "3. Singleton Pattern",
5 | "proxy-pattern": "4. Proxy Pattern",
6 | "observer-pattern": "5. Observer Pattern",
7 | "factory-pattern": "6. Factory Pattern",
8 | "prototype-pattern": "7. Prototype Pattern"
9 | }
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/module-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Module Pattern
5 |
6 | Split up your code into smaller, reusable pieces
7 |
8 | ---
9 |
10 | ## Overview
11 |
12 | ES2015 introduced built-in JavaScript modules. A module is a file containing JavaScript code and makes it easy to expose and hide certain values.
13 |
14 | The module pattern is a great way to split a larger file into **multiple smaller**, **reusable pieces**. It also promotes **code encapsulation**, since the values within modules are kept private inside the module by default, and cannot be modified. Only the values that are explicitly exported with the `export` keyword are accessible to other files.
15 |
16 |
22 |
23 | The module pattern provides a way to have both public and private pieces with the `export` keyword. This protects values from leaking into the global scope or ending up in a naming collision.
24 |
25 |
31 |
32 | In the above example, the `secret` variable is accessible within the module, but not outside of it. Other modules cannot import this value, as it hasn't been `export`ed.
33 |
34 | ---
35 |
36 | Without modules, we can access properties defined in scripts that are loaded prior to the currently running script.
37 |
38 |
44 |
45 | With modules however, we can only use the values that have been exported.
46 |
47 | ---
48 |
49 | ## Implementation
50 |
51 | There are a few ways we can use modules.
52 |
53 | ### HTML tag
54 |
55 | When adding JavaScript to HTML files directly, you can use modules by adding the `type="module"` attribute to the `script` tags.
56 |
57 |
63 |
64 |
70 |
71 | ### Node
72 |
73 | In Node, you can use ES2015 modules either by:
74 |
75 | - Using the `.mjs` extension
76 | - Adding `"type": "module"` to your `package.json`
77 |
78 |
79 |
80 | ---
81 |
82 | ## Tradeoffs
83 |
84 |
85 | Encapsulation: The values within a module are scoped to that specific
86 | module. Values that aren't explicitly exported are not available outside of
87 | the module.
88 |
89 |
90 |
91 | Reusability: We can reuse modules throughout our application
92 |
93 |
94 | ---
95 |
96 | ## Exercise
97 |
98 | ### Challenge
99 |
100 | 1. Split the code below up into two pieces, `math.js` that includes the `add`, `subtract`, `divide` and `multiply` methods. The `index.js` file contains the remaining functionality.
101 | 2. Use ES2015 modules to export the `add`, `subtract`, `divide` and `multiply` methods from the `math.js` file, and import them in `index.js`
102 |
103 |
104 |
105 | ---
106 |
107 | ### Solution
108 |
109 |
110 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/observer-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Observer Pattern
5 |
6 | Use observables to notify subscribers when an event occurs
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | With the observer pattern, we have:
13 |
14 | 1. An **observable object**, which can be observed by subscribers in order to notify them.
15 | 2. **Subscribers**, which can subscribe to and get notified by the observable object.
16 |
17 | For example, we can add the `logger` as a subscriber to the observable.
18 |
19 |
31 |
32 | When the `notify` method is invoked on the observable, all subscribers get invoked and (optionally) pass the data from the notifier to them.
33 |
34 |
46 |
47 | ---
48 |
49 | ### Implementation
50 |
51 | We can export a singleton Observer object, which contains a `notify`, `subscribe`, and `unsubscribe` method.
52 |
53 | ```js
54 | const observers = [];
55 |
56 | export default Object.freeze({
57 | notify: (data) => observers.forEach((observer) => observer(data)),
58 | subscribe: (func) => observers.push(func),
59 | unsubscribe: (func) => {
60 | [...observers].forEach((observer, index) => {
61 | if (observer === func) {
62 | observers.splice(index, 1);
63 | }
64 | });
65 | },
66 | });
67 | ```
68 |
69 | We can use this observable throughout the entire application, making it possible to subscribe functions to the observable
70 |
71 | ```js
72 | import Observable from "./observable";
73 |
74 | function logger(data) {
75 | console.log(`${Date.now()} ${data}`);
76 | }
77 |
78 | Observable.subscribe(logger);
79 | ```
80 |
81 | and notify all subscribers based on certain events.
82 |
83 | ```js
84 | import Observable from "./observable";
85 |
86 | document.getElementById("my-button").addEventListener("click", () => {
87 | Observable.notify("Clicked!"); // Notifies all subscribed observers
88 | });
89 | ```
90 |
91 |
92 |
93 | ---
94 |
95 | ### Tradeoffs
96 |
97 |
98 | Separation of Concerns: The observer objects aren't tightly coupled to
99 | the observable object, and can be (de)coupled at any time. The observable
100 | object is responsible for monitoring the events, while the observers simply
101 | handle the received data.
102 |
103 |
104 |
105 | Decreased performance: Notifying all subscribers might take a
106 | significant amount of time if the observer handling becomes too complex, or if
107 | there are too many subscibers to notify.
108 |
109 |
110 | ---
111 |
112 | ### Exercise
113 |
114 | The two buttons in our application both send events to a fake analytics provider.
115 |
116 | #### Challenge
117 |
118 | Create an observer to which the buttons can subscribe. When a user clicks on a button, this event should get sent to the fake analytics provider.
119 |
120 |
121 |
122 | ---
123 |
124 | #### Solution
125 |
126 |
127 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/prototype-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Prototype Pattern
5 |
6 | Share properties among many objects of the same type
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | If we want to share properties among many objects of the same type, we can use the Prototype pattern.
13 |
14 | 
15 |
16 | ---
17 |
18 | ### Implementation
19 |
20 | Say we wanted to create many dogs with a `createDog` factory function.
21 |
22 | ```js
23 | const createDog = (name, age) => ({
24 | name,
25 | age,
26 | bark() {
27 | console.log(`${name} is barking!`);
28 | },
29 | wagTail() {
30 | console.log(`${name} is wagging their tail!`);
31 | },
32 | });
33 |
34 | const dog1 = createDog("Max", 4);
35 | const dog2 = createDog("Sam", 2);
36 | const dog3 = createDog("Joy", 6);
37 | const dog4 = createDog("Spot", 8);
38 | ```
39 |
40 | This way, we can easily create many dog objects with the same properties.
41 |
42 | 
43 |
44 | ---
45 |
46 | However, we're unnecessarily adding a new `bark` and `wagTail` methods to each dog object. Under the hood, we're creating two new functions for each dog object, which uses memory.
47 |
48 | We can use the Prototype Pattern to share these methods among many dog objects.
49 |
50 | ```js
51 | class Dog {
52 | constructor(name, age) {
53 | this.name = name;
54 | this.age = age;
55 | }
56 |
57 | bark() {
58 | console.log(`${this.name} is barking!`);
59 | }
60 | wagTail() {
61 | console.log(`${this.name} is wagging their tail!`);
62 | }
63 | }
64 |
65 | const dog1 = new Dog("Max", 4);
66 | const dog2 = new Dog("Sam", 2);
67 | const dog3 = new Dog("Joy", 6);
68 | const dog4 = new Dog("Spot", 8);
69 | ```
70 |
71 | 
72 |
73 | ES6 classes allow us to easily share properties among many instances, `bark` and `wagTail` in this case.
74 |
75 | ##
76 |
77 | ### Tradeoffs
78 |
79 |
80 | Memory efficient: The prototype chain allows us to access properties
81 | that aren't directly defined on the object itself, we can avoid duplication of
82 | methods and properties, thus reducing the amount of memory used.
83 |
84 |
85 |
86 | Readaibility: When a class has been extended many times, it can be difficult to know where certain properties come from.
87 |
88 | For example, if we have a `BorderCollie` class that extends all the way to the `Animal` class, it can be difficult to trace back where certain properties came from.
89 |
90 | 
91 |
92 |
93 |
94 | ---
95 |
96 | ### Exercise
97 |
98 | #### Challenge
99 |
100 | Refactor this factory function to a class.
101 |
102 | {" "}
103 |
104 | ---
105 |
106 | #### Solution
107 |
108 | {" "}
114 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/proxy-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Proxy Pattern
5 |
6 | Intercept and control interactions to target objects
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | The **Proxy Pattern** uses a **Proxy** intercept and control interactions to target objects.
13 |
14 | Let's say that we have a `person` object. We can access properties with either dot or bracket notation,
15 |
16 |
28 |
29 | and modify property values in a similar fashion.
30 |
31 |
43 |
44 | With the Proxy pattern, we don't want to interact with this object directly. Instead, a Proxy object intercepts the request, and (optionally) forwards this to the target object - the `person` object in this case.
45 |
46 |
58 |
59 | ---
60 |
61 | ### Implementation
62 |
63 | In JavaScript, we can easily create a new proxy by using the built-in `Proxy` object.
64 |
65 | 
66 |
67 | The `Proxy` object receives two arguments:
68 |
69 | 1. The target object
70 | 2. A handler object, which we can use to add functionality to the proxy. This object comes with some built-in functions that we can use, such as `get` and `set`.
71 |
72 | The `get` method on the handler object gets invoked when we want to access a property, and the `set` method gets invoked when we want to modify a property.
73 |
74 | ```js
75 | const person = {
76 | name: "John Doe",
77 | age: 42,
78 | email: "john@doe.com",
79 | country: "Canada",
80 | };
81 |
82 | const personProxy = new Proxy(person, {
83 | get: (target, prop) => {
84 | console.log(`The value of ${prop} is ${target[prop]}`);
85 | return target[prop];
86 | },
87 | set: (target, prop, value) => {
88 | console.log(`Changed ${prop} from ${target[prop]} to ${value}`);
89 | target[prop] = value;
90 | return true;
91 | },
92 | });
93 | ```
94 |
95 | ---
96 |
97 | #### `Reflect`
98 |
99 | The built-in `Reflect` object makes it easier to manipulate the target object.
100 |
101 | Instead of accessing properties through `obj[prop]` or setting properties through `obj[prop] = value`, we can access or modify properties on the target object through `Reflect.get()` and `Reflect.set()`. The methods receive the same arguments as the methods on the handler object.
102 |
103 |
115 |
116 | ---
117 |
118 | ### Tradeoffs
119 |
120 |
121 | Control: Proxies make it easy to add functionality when interacting
122 | with a certain object, such as validation, logging, formatting, notifications,
123 | debugging.
124 |
125 |
126 |
127 | Long handler execution: Executing handlers on every object interaction
128 | could lead to performance issues.
129 |
130 |
131 | ---
132 |
133 | ### Exercise
134 |
135 | #### Challenge
136 |
137 | Add the following validation to the `user` object:
138 |
139 | - The `username` property has to be a `string` that only contains of letters, and is at least 3 characters long
140 | - The `email` property has to be a valid email address.
141 | - The `age` property has to be a number, and has to be at least `18`
142 | - When a property is retrieved, change the output to `${new Date()} | The value of ${property}} is ${target[property]}`. For example if we get `user.name`, it needs to log `2022-05-31T15:29:15.303Z | The value of name is John`
143 |
144 |
145 |
146 | #### Solution
147 |
148 |
149 |
--------------------------------------------------------------------------------
/pages/patterns/design-patterns/singleton-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Bleed from "nextra-theme-docs/bleed";
2 | import Callout from "../../../components/Callout";
3 | import StackBlitz from "../../../components/StackBlitz";
4 |
5 | # Singleton Pattern
6 |
7 | Share a single global instance throughout our application
8 |
9 | ---
10 |
11 | ### Overview
12 |
13 | With the Singleton Pattern, we restrict the instantiation of certain classes to one single instance. This single instance is unmodifiable, and can be accessed globally throughout the application.
14 |
15 | For example, we can have a `Counter` singleton, which contains a `getCount`, `increment`, and `decrement` method.
16 |
17 | 
18 |
19 | This singleton can be shared globally across multiple files within the application. The imports of this Singleton all reference the same instance.
20 |
21 | The `increment` method increments the value of `counter` by 1. Any time the `increment` method has been invoked anywhere in the application, thus changing the value of `counter`, the change is reflected throughout the entire application.
22 |
23 |
35 |
36 | ---
37 |
38 | ### Implementation
39 |
40 | In ES6, there are several ways to create Singletons.
41 |
42 | #### Classes
43 |
44 | Creating a singleton with an ES2015 `class` can be done by:
45 |
46 | ```js highlight="1,3-5"
47 | let instance;
48 |
49 | // 1. Creating the `Counter` class, which contains a `constructor`, `getInstance`, `getCount`, `increment` and `decrement` method.
50 | // Within the constructor, we check to make sure the class hasn't already been instantiated.
51 | class Counter {
52 | constructor() {
53 | if (instance) {
54 | throw new Error("You can only create one instance!");
55 | }
56 | this.counter = counter;
57 | instance = this;
58 | }
59 |
60 | getCount() {
61 | return this.counter;
62 | }
63 |
64 | increment() {
65 | return ++this.counter;
66 | }
67 |
68 | decrement() {
69 | return --this.counter;
70 | }
71 | }
72 |
73 | // 2. Setting a variable equal to the the frozen newly instantiated object, by using the built-in `Object.freeze` method.
74 | // This ensures that the newly created instance is not modifiable.
75 | const singletonCounter = Object.freeze(new Counter());
76 |
77 | // 3. Exporting the variable as the `default` value within the file to make it globally accessible.
78 | export default singletonCounter;
79 | ```
80 |
81 | ---
82 |
83 | #### Objects
84 |
85 | We can also directly create objects without having to use a `class`, which can lead to much simpler and cleaner code.
86 |
87 | To create a singleton using a regular object, we have to:
88 |
89 | ```js
90 | let counter = 0;
91 |
92 | // 1. Create an object containing the `getCount`, `increment`, and `decrement` method.
93 | const counterObject = {
94 | getCount: () => counter,
95 | increment: () => ++counter,
96 | decrement: () => --counter,
97 | };
98 |
99 | // 2. Freeze the object using the `Object.freeze` method, to ensure the object is not modifiable.
100 | const singletonCounter = Object.freeze(counterObject);
101 |
102 | // 3. Export the object as the `default` value to make it globally accessible.
103 | export default singletonCounter;
104 | ```
105 |
106 | We could even export the frozen object directly, without having to declare multiple variables.
107 |
108 | ```js
109 | let counter = 0;
110 |
111 | export default Object.freeze({
112 | getCount: () => counter,
113 | increment: () => ++counter,
114 | decrement: () => --counter,
115 | });
116 | ```
117 |
118 | ---
119 |
120 | ### Tradeoffs
121 |
122 |
123 | Memory: Restricting the instantiation to just one instance could
124 | potentially save a lot of memory space. Instead of having to set up memory for
125 | a new instance each time, we only have to set up memory for that one instance,
126 | which is referenced throughout the application.
127 |
128 |
129 |
130 | Unnecessary: ES2015 Modules are singletons by default. We no longer
131 | need to explicitly create singletons to achieve this global, non-modifiable
132 | behavior.
133 |
134 |
135 |
136 | Depedency Hiding: When importing another module, it may not always be
137 | obvious that that module is importing a Singleton. This could lead to
138 | unexpected value modification within the Singleton, which would be reflected
139 | throughout the application.
140 |
141 |
142 |
143 | Global Scope Pollution: The global behavior of Singletons is
144 | essentially the same as a global variable. Global Scope Pollution can end up
145 | in accidentally overwriting the value of a global variable, which can lead to
146 | a lot of unexpected behavior. Usually, certain parts of the codebase modify
147 | the values within global state, whereas others consume that data. The order of
148 | execution here is important, understanding the data flow when using a global
149 | state can get very tricky as your application grows, and dozens of components
150 | rely on each other.
151 |
152 |
153 |
154 | Testing: Since we can't create new instances each time, all tests rely
155 | on the modification to the global instance of the previous test. The order of
156 | the tests matter in this case, and one small modification can lead to an
157 | entire test suite failing. After testing, we need to reset the entire instance
158 | in order to reset the modifications made by the tests.
159 |
160 |
161 | ---
162 |
163 | ### Exercise
164 |
165 | #### Challenge
166 |
167 | Turn this class into a singleton, to ensure that only one `DBConnection` instance can exist.
168 |
169 |
170 |
171 | #### Solution
172 |
173 |
174 |
--------------------------------------------------------------------------------
/pages/patterns/index.mdx:
--------------------------------------------------------------------------------
1 | import Features from "../../components/Features";
2 | import Link from "next/link";
3 |
4 | # JavaScript Patterns Workshop
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 | ## Introduction
21 |
22 | This website is related to the [FrontendMasters workshop on JavaScript Patterns](https://frontendmasters.com/courses/tour-js-patterns/), which is split up into four parts:
23 |
24 |
25 |
26 | The content is based on [Patterns.dev](https://www.patterns.dev) - a free online resource on design patterns and component patterns for building powerful web apps with vanilla JavaScript and React.
27 |
28 | ---
29 |
30 | The patterns covered on this website and in the workshop can guide you when facing a problem other developers have encountered many times before, but are not a blunt tool for jamming into every scenario. The goal is to raise awareness to certain patterns, the problems they solve, and their implementation.
31 |
--------------------------------------------------------------------------------
/pages/patterns/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Introduction",
3 | "design-patterns": "🧩 ⏐ Design Patterns",
4 | "react-patterns": "⚛️ ⏐ React Patterns",
5 | "performance-patterns": "⚡️ ⏐ Performance Patterns",
6 | "rendering-patterns": "⚙️ ⏐ Rendering Patterns"
7 | }
8 |
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/browser-hints.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Browser Hints
5 |
6 | Use hints to inform the browser about (optionally) critical resources
7 |
8 | ---
9 |
10 | ## Prefetch
11 |
12 | Fetch and cache resources that may be requested some time soon
13 |
14 | ### Overview
15 |
16 | The `prefetch` browser hint can be used to fetch resources that may be needed some time soon, but not immediately on the initial load. This can be the case on subsequent requests or page navigations that a user is likely to make.
17 |
18 | A prefetched resource is fetched when the browser is idle and has calculated that it's got enough bandwidth, after which it caches the prefetched resource. When the client actually needs the resource, it can easily get it from cache instead of having to make a request to the server.
19 |
20 | 
21 |
22 | For example if we use route-based splitting, and we know that most users will navigate to the `/about` route, we can prefetch this route to get a faster navigation, resulting in a better UX.
23 |
24 |
30 |
31 | Instead of waiting for a user interaction to fetch the `about.bundle.js` bundle, the browser prefetched this resource when it was idle. Once the user actually navigates to the `/about` page, the bundle can quickly load from cache instead of making a request to the server.
32 |
33 | ---
34 |
35 | ### Implementation
36 |
37 | We can prefetch a resource by explicitly adding it to the `head` of the `html` document.
38 |
39 | ```html
40 |
41 | ```
42 |
43 | If you're using Webpack, you can prefetch it dynamically by using the `/* webpackPrefetch: true */` magic comment.
44 |
45 | ```js
46 | const About = lazy(() => import(/* webpackPrefetch: true */ "./about"));
47 | ```
48 |
49 |
50 |
51 | ---
52 |
53 | ### Tradeoffs
54 |
55 |
56 | Loading time: The browser can quickly load and render the component
57 | from cache, instead of having to make a request to the server.
58 |
59 |
60 |
61 | Unnecessary: If the user ended up never navigating to the `About`
62 | route, we unnecessarily loaded the resource, which could negatively affect the
63 | app's performance and be costly to the user.
64 |
65 |
66 | ---
67 |
68 | ## Preload
69 |
70 | Inform the browser of critical resources before they are discovered
71 |
72 | ### Overview
73 |
74 | The `preload` browser hint can be used to fetch resources that are critical to the current navigation, such as fonts or images are instantly (not longer than 3 seconds after the initial load) visible on a landing page.
75 |
76 | 
77 |
78 | With `prefetch`, the browser would only actually prefetch the resource if the conditions were good enough to not negatively affect the user experience. Unlike a prefetch, a preloaded resource gets fetched no matter what.
79 |
80 | For example, if we wanted to show the `SearchFlyout` instantly on the landing page, we can preload this resource to make sure the user won't have to wait too long before this resource is available.
81 |
82 |
88 |
89 | The `search-flyout.bundle.js` bundle is fetched in parallel with the `main.bundle.js`. When the client actually needs to render the resource, which happens soon after the initial load, it can quickly retrieve it from the HTTP cache.
90 |
91 | ---
92 |
93 | ### Implementation
94 |
95 | We can preload a resource by explicitly adding it to the `head` of the `html` document.
96 |
97 | ```html
98 |
99 | ```
100 |
101 | If you're using Webpack, you can preload it dynamically by using the `/* webpackPreload: true */` magic comment.
102 |
103 | ```js
104 | const SearchFlyout = lazy(() =>
105 | import(/* webpackPreload: true */ "./SearchFlyout")
106 | );
107 | ```
108 |
109 | ---
110 |
111 | ### Tradeoffs
112 |
113 |
114 | Loading time: The browser can quickly load and render the component
115 | from cache, instead of having to make a request to the server.
116 |
117 |
118 |
119 | Layout shift: Preloading styles, fonts and images can reduce layout
120 | shift.
121 |
122 |
123 |
124 | Performance: Since preloaded assets will get fetched no matter what, it's important to only preload resources that are actually instantly necessary. In most cases, it's worth either `prefetch`ing the resource, or if it's JavaScript, using a `script` with an `async` or `defer` attribute.
125 |
126 | ```js
127 |
128 |
129 | ```
130 |
131 |
132 |
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/dynamic-import.mdx:
--------------------------------------------------------------------------------
1 | import StackBlitz from "../../../components/StackBlitz";
2 | import Callout from "../../../components/Callout";
3 |
4 | # Dynamic Import
5 |
6 | Import parts of your code on demand
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | Statically imported modules are all included in the final bundle of our app, even components that don't need to be rendered right away. Depending on the size of the components and the final bundle, this could lead to a worse initial loading experience, as the client has to download and parse the entire bundle.
13 |
14 | In many cases, we can defer the import of modules until they're actually needed, which results in smaller bundles.
15 |
16 | Let's say for example that we have a `Search` input component. When a user clicks on the search input, we show a `SearchPopup` component that shows some popular locations.
17 |
18 |
24 |
25 | The `SearchPopup` component isn't instantly visible on the screen - or maybe won't even be visible at all if the user never clicks on the `SearchInput`. We can dynamically import the `SearchPopUp` component, which separates this code from the initial bundle, and creates a separate bundle for just this component.
26 |
27 | ---
28 |
29 | ### Implementation
30 |
31 | In React, we can dynamically load a component by using `React.Suspense` with `React.lazy`.
32 |
33 |
39 |
40 | The `Suspense` component receives a `fallback`, that gets rendered while the client is fetching the `SearchPopup` bundle.
41 |
42 | In this example, `Card1` and `Card2` are statically imported and included in the initial bundle. `Card3` and `Card4` however are dynamically loaded on user interaction.
43 |
44 |
45 |
46 | ---
47 |
48 | ### Tradeoffs
49 |
50 |
51 | Faster initial load: Dynamically importing modules reduces the initial
52 | bundle size - allowing for a smaller initial load since the client doesn't
53 | have to download and execute as much, saving bandwidth.
54 |
55 |
56 |
57 | Layout shift: A layout shift can occur if your fallback component and
58 | the component that eventually gets rendered differ a lot in size.
59 |
60 |
61 |
62 | User experience: If you're lazy-loading a component that's needed for
63 | the initial render, it may unnecessarily result in longer loading times. Try
64 | to only lazy load components that aren't visible on the initial render.
65 |
66 |
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/import-on-visibility.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Import on Visibility
5 |
6 | Load non-critical components when they are visible in the viewport
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | We just saw how we can dynamically import components base on user interaction. However, we can also dynamically import components based on their visibility within the viewport.
13 |
14 | For example, if we wanted to show the listings on smaller viewports, not all listings are instantly visible to the user.
15 |
16 |
22 |
23 | Instead, we can lazy-load the listings, and only load them when they're visible in the viewport when the user scrolls down.
24 |
25 |
31 |
32 | ---
33 |
34 | ### Implementation
35 |
36 | One way to dynamically import components on interaction, is by using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). There's a React hook called [`react-intersection-observer`](https://www.npmjs.com/package/react-intersection-observer) that we can use to easily detect whether a component is visible in the viewport.
37 |
38 | Lazy-loading the `Footer` component would result in something like this:
39 |
40 | ```js
41 | import { Suspense, lazy } from "react";
42 | import { useInView } from "react-intersection-observer";
43 | const Listing = lazy(() => import("./components/Listing"));
44 |
45 | function ListingCard(props) {
46 | const { ref, inView } = useInView();
47 |
48 | return (
49 |
50 | }>{inView && }
51 |
52 | );
53 | }
54 | ```
55 |
56 |
57 |
58 | ---
59 |
60 | ### Tradeoffs
61 |
62 |
63 | Faster initial load: Dynamically importing modules reduces the initial
64 | bundle size - allowing for a smaller initial load since the client doesn't
65 | have to download and execute as much, saving bandwidth.
66 |
67 |
68 |
69 | Layout shift: A layout shift can occur if your fallback component and
70 | the component that eventually gets rendered differ a lot in size.
71 |
72 |
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/introduction.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 |
3 | # Performance Patterns
4 |
5 | Performance patterns can be used to achieve a better user and developer experience.
6 |
7 | ---
8 |
9 | Any client-side JavaScript in our application has to be shipped to the client in one way or another. Before this can happen, we have to make sure that:
10 |
11 | 1. The JavaScript is executable in a browser environment
12 | 2. The file that the browser has to download and fetch only contains relevant code, to ensure the browser can quickly fetch this bundle without using too much bandwidth
13 |
14 | ---
15 |
16 | ## Tools
17 |
18 | ### Bundlers
19 |
20 | A bundler bundles our application together in one or multiple files, and makes it possible to make code executable in other environments, for example browsers. A bundler receives an `entry` file, from which it starts to bundle the code together. If we're importing modules from other files, the bundler traverses these modules in order to include them all in the bundle.
21 |
22 |
28 |
29 | How the bundler creates the output file(s) is entirely configurable.
30 |
31 | Popular bundlers:
32 |
33 | - [Webpack](https://webpack.js.org/) (We'll be using this one in the following examples)
34 | - [Parcel](https://parceljs.org/)
35 | - [Rollup](https://rollupjs.org/guide/en/)
36 |
37 | ---
38 |
39 | ### Compilers
40 |
41 | A compiler converts JavaScript (or TypeScript) code into another version of JavaScript, which could be backwards compatible in current and older browsers or environments.
42 |
43 | For example, we _can_ use Private class features in JavaScript, but not all browsers support it yet. If we use Private class features in our code, we need to use a compiler to compile it to a version of JavaScript that a browser (or other environment, for example in a Serverless Function) can execute.
44 |
45 |
46 | A compiler does *not* bundle code. It simply transforms it to another version,
47 | based on a certain configuration.
48 |
49 |
50 | 
51 |
52 | Popular compilers:
53 |
54 | - [Babel](https://babeljs.io/)
55 | - [TypeScript](https://www.typescriptlang.org/)
56 |
57 | ---
58 |
59 | ### Minifiers
60 |
61 | A minifier can reduce the size of a JavaScript file based on a certain configuration, for example by removes comments, making variable and function names smaller, removing whitespace, and so on. This allows us to have a much smaller bundle size and faster execution without sacrificing readability, while the JavaScript executes and behaves the exact same way.
62 |
63 | For example, we can minify the compiled result we previously got after compiling our code with Babel.
64 |
65 | 
66 |
67 | Popular minifiers:
68 |
69 | - [Terser](https://terser.org/)
70 | - [Uglify](https://www.uglifyjs.net/)
71 |
72 | ---
73 |
74 | ### Combination
75 |
76 | When working with bundlers, for example Webpack, we have to configure Webpack to include a compiler like Babel, and add optimizations like the Terser minifier.
77 |
78 | There are also tools out there that combine all these steps, such as:
79 |
80 | - [SWC](https://swc.rs/) - a Rust-based compiler, bundler, and minifier
81 | - [ESBuild](https://esbuild.github.io/) - a Go-based compiler, bundler, and minifier.
82 |
83 | ---
84 |
85 | ## Practices
86 |
87 | ### Bundle Splitting
88 |
89 | Bundle splitting is the process of creating multiple, smaller bundles rather than one large bundle.
90 |
91 | A larger bundle can lead to an increased amount of loading time, processing time, and execution time. Users on low-end devices or slower networks will see a significant increase in loading time before the bundle has been fetched.
92 |
93 | To avoid larger bundles, we can tell the bundler to create multiple, smaller bundles, instead of bundling everything into one big file.
94 |
95 |
101 |
102 | ### Tree Shaking
103 |
104 | With tree shaking, we can reduce the bundle size by eliminating dead code. Tree shaking is aimed at removing unused code from a JavaScript bundle. Bundlers can automatically detect dead code, to exclude this code from the final bundle.
105 |
106 | For example, if two methods are exported from the `input.js` file, namely `validateInput` and `formatInput`, but we're only importing `validateInput`, the bundler will ensure that the `formatInput` method won't be included in the final bundle.
107 |
108 | ```js
109 | // input.js
110 | export function validateInput(input) {
111 | const isValid = input.length > 10;
112 | return isValid;
113 | }
114 |
115 | export function formatInput(input) {
116 | const formattedInput = input.toLowerCase();
117 | return formattedInput;
118 | }
119 | ```
120 |
121 | ```js
122 | // index.js
123 | import { validateInput } from "./input";
124 |
125 | const input = document.getElementById("input");
126 | const btn = document.getElementById("btn");
127 |
128 | btn.addEventListener("click", () => {
129 | validateInput(input.value);
130 | });
131 | ```
132 |
133 | After tree shaking, the final bundle won't include the `formatInput` function as it's not referenced in the code.
134 |
135 | 
136 |
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "introduction": "1. Bundling, compiling, minifying, tree-shaking",
3 | "static-import": "2. Static Import",
4 | "dynamic-import": "3. Dynamic Import",
5 | "import-on-visibility": "4. Import on Visibility",
6 | "route-based-splitting": "5. Route-based Splitting",
7 | "browser-hints": "6. Browser Hints"
8 | }
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/route-based-splitting.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Route Based Splitting
5 |
6 | Dynamically load components based on the current route
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | If your application has multiple pages, we can use dynamic imports to only load the resources that are needed for the current route. Instead of the code for all the possible pages in the initial bundle, we can bundle-split based on routes. This approach allows us to defer loading the bundle until the user actually navigates to that page.
13 |
14 |
20 |
21 | ---
22 |
23 | ### Implementation
24 |
25 | If you're using `react-router` for navigation, you can wrap the `Switch` component in a `React.Suspense`, and import the routes using `React.lazy`. This automatically enables route-based code splitting.
26 |
27 | ```js
28 | import React, { lazy, Suspense } from "react";
29 | import { Switch, Route, BrowserRouter as Router } from "react-router-dom";
30 |
31 | const App = lazy(() => import("./App"));
32 | const About = lazy(() => import("./About"));
33 | const Contact = lazy(() => import("./Contact"));
34 |
35 | ReactDOM.render(
36 |
37 | Loading...}>
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ,
51 | document.getElementById("root")
52 | );
53 | ```
54 |
55 |
56 |
57 | ---
58 |
59 | ### Tradeoffs
60 |
61 |
62 | Faster initial load: Dynamically importing the pages reduces the
63 | initial bundle size - allowing for a smaller initial load since the client
64 | doesn't have to download and execute as much, saving bandwidth.
65 |
66 |
--------------------------------------------------------------------------------
/pages/patterns/performance-patterns/static-import.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 |
3 | # Static Import
4 |
5 | Import code that has been exported by another module
6 |
7 | ---
8 |
9 | ### Overview
10 |
11 | A statically imported module is a module that's imported with the default `import` keyword.
12 |
13 | ```js
14 | import module1 from "./module1";
15 | import module2 from "./module2";
16 | import module3 from "./module3";
17 | ```
18 |
19 | In this case, `module1`, `module2`, and `module3` are statically imported.
20 |
21 | ---
22 |
23 | When a module is statically imported, a bundler traverses all the modules, and bundles them into one file.
24 |
25 | Let's say we have four files:
26 |
27 | 1. `module1.js`, which exports as single function `module1`
28 | 2. `module2.js`, which statically imports the named export `module1`, and returns a single function `module2`
29 | 3. `module3.js`, which statically imports the named export `module2`, and returns a single function `module3`
30 | 4. `index.js`, which statically imports the named export `module3` and logs this value
31 |
32 | When we bundle `index.js`, the bundler traverses the modules, and bundles them all together into one single file.
33 |
34 |
40 |
41 | ---
42 |
43 | ### Implementation
44 |
45 | We can import React components from other files.
46 |
47 | ```js
48 | import Header from "./Header";
49 | import Content from "./Content";
50 |
51 | export default function BlogPost({ post }) {
52 | return (
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 | ```
61 |
62 | In this case, `Header` and `Content` are statically imported. The `Header` and `Content` modules get executed as soon as the engine reaches the line on which we import them.
63 |
64 | When you open the console, you can see the order in which the modules have been loaded!
65 |
66 | ---
67 |
68 | ### Tradeoffs
69 |
70 |
71 | Loading instant dependencies: Statically imported components are
72 | instantly available to the user
73 |
74 |
75 |
76 | Optimizations: Statically imported modules can be statically analyzed
77 | and tree-shaken.
78 |
79 |
80 |
81 | Large bundle size: When importing all modules, you might include code
82 | that won't be necessary
83 |
84 |
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/compound-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Compound Pattern
5 |
6 | Create multiple components that work together to perform a single task
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | With the Compound Pattern, we can create multiple components that work together to perform one single task.
13 |
14 | Let's say for example that we have a `Search` input component. When a user clicks on the search input, we show a `SearchPopup` component that shows some popular locations.
15 |
16 |
22 |
23 | To create this behavior, we can create a `FlyOut` compound component.
24 |
25 |
31 |
32 | This `FlyOut` component is an example of a compound component, as it also exposes some sub-components that all work together to toggle and render the `FlyOut` component.
33 |
34 | ```js
35 | import React from "react";
36 | import { FlyOut } from "./FlyOut";
37 |
38 | export default function SearchInput() {
39 | return (
40 |
41 |
42 |
43 |
44 | San Francisco, CA
45 |
46 | Seattle, WA
47 | Austin, TX
48 | Miami, FL
49 | Boulder, CO
50 |
51 |
52 | );
53 | }
54 | ```
55 |
56 | The `FlyOut` compound component is a stateful component - which means we don't have to add the stateful logic to the `SearchInput` component.
57 |
58 | ---
59 |
60 | ### Implementation
61 |
62 | We can implement the Compound pattern using either a Provider, or `React.Children.map`.
63 |
64 | #### Provider
65 |
66 | The `FlyOut` compound component consists of:
67 |
68 | - `FlyoutContext` to keep track of the visbility state of `FlyOut`
69 | - `Input` to toggle the `FlyOut`'s `List` component's visibility
70 | - `List` to render the `FlyOut`'s `ListItems`s
71 | - `ListItem` that gets rendered within the `List`.
72 |
73 | ```js
74 | const FlyOutContext = React.createContext();
75 |
76 | export function FlyOut(props) {
77 | const [open, setOpen] = React.useState(false);
78 | const [value, setValue] = React.useState("");
79 | const toggle = React.useCallback(() => setOpen((state) => !state), []);
80 |
81 | return (
82 |
83 |
;
104 | }
105 |
106 | FlyOut.Input = Input;
107 | FlyOut.List = List;
108 | FlyOut.ListItem = ListItem;
109 | ```
110 |
111 | Although we didn't have to name our compound component's sub-components `FlyOut.`, it's an easy way to identify compound components, and only requires a single import.
112 |
113 | ---
114 |
115 | #### `React.Children.map`
116 |
117 | Another way to implement the Compound pattern, is to use `React.Children.map` in combination with `React.cloneElement`. Instead of having to use the Context API like in the previous example, we now have access to these two values through `props`.
118 |
119 | ```js
120 | export function FlyOut(props) {
121 | const [open, setOpen] = React.useState(false);
122 | const [value, setValue] = React.useState("");
123 | const toggle = React.useCallback(() => setOpen((state) => !state), []);
124 |
125 | return (
126 |
;
150 | }
151 |
152 | FlyOut.Input = Input;
153 | FlyOut.List = List;
154 | FlyOut.ListItem = ListItem;
155 | ```
156 |
157 | All children components are cloned, and passed the value of `open`, `toggle`, `value` and `setValue`.
158 |
159 |
160 |
161 | ---
162 |
163 | ### Tradeoffs
164 |
165 |
166 | State management: Compound components manage their own internal state,
167 | which they share among the several child components. When implementing a
168 | compound component, we don't have to worry about managing the state ourselves.
169 |
170 |
171 |
172 | Single import: When importing a compound component, we don't have to
173 | explicitly import the child components that are available on that component.
174 |
175 |
176 |
177 | Nested components: When using `React.Children.map`, only direct children of the parent component will have access to the open and toggle props, meaning we can't wrap any of these components in another component.
178 |
179 | ```js
180 | function FlyoutMenu() {
181 | return (
182 |
183 | {/* This breaks, since the direct child of FlyOut is now a div */}
184 |
185 |
186 |
187 | San Francisco, CA
188 | Seattle, WA
189 |
190 |
191 |
192 | );
193 | }
194 | ```
195 |
196 |
197 |
198 |
199 | Naming collisions: Cloning an element with `React.cloneElement`
200 | performs a shallow merge. If we pass a prop that already exists on the
201 | component, in this example `open` or `toggle`, a naming collision occurs, and
202 | the value of these props will be overwritten with the latest value that we
203 | pass.
204 |
205 |
206 | ---
207 |
208 | ### Challenge
209 |
210 | #### Exercise
211 |
212 | Create a `FlyOut` component that gets rendered by the `Input` component. This component should contain:
213 |
214 | - `FlyOut.Input` to render the input field
215 | - `FlyOut.List` to render the list items
216 | - `FlyOut.ListItem` to render the list items
217 |
218 |
219 |
220 | #### Solution
221 |
222 |
223 |
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/conpres.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Container/Presentational Pattern
5 |
6 | Enforce separation of concerns by separating the view from the application logic.
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | We can use the Container/Presentational pattern to separate the logic of a component from the view. To achieve this, we need to have a:
13 |
14 | - **Presentational** Component, that cares about **how** data is shown to the user.
15 | - **Container** Component, that cares about **what** data is shown to the user.
16 |
17 | For example, if we wanted to show listings on the landing page, we could use a container component to fetch the data for the recent listings, and use a presentational component to actually render this data.
18 |
19 |
31 |
32 | ---
33 |
34 | ### Implementation
35 |
36 | We can implement the Container/Presentational pattern using either Class Components or functional components.
37 |
38 | ```js
39 | import React from "react";
40 | import { LoadingListings, Listing, ListingsGrid } from "../components";
41 |
42 | function ListingsContainerComponent() {
43 | const [listings, setListings] = React.useState([]);
44 |
45 | React.useEffect(() => {
46 | fetch("https://my.cms.com/listings")
47 | .then((res) => res.json())
48 | .then((res) => setListings(res.listings));
49 | }, []);
50 |
51 | return ;
52 | }
53 |
54 | function ListingsPresentationalComponent(props) {
55 | if (props.listings.length === 0) {
56 | return ;
57 | }
58 |
59 | return (
60 |
61 | {listings.map((listing) => (
62 |
63 | ))}
64 |
65 | );
66 | }
67 | ```
68 |
69 | ---
70 |
71 | ### Tradeoffs
72 |
73 |
74 | Separation of concerns: Presentational components can be pure functions
75 | which are responsible for the UI, whereas container components are responsible
76 | for the state and data of the application. This makes it easy to enforce the
77 | separation of concerns.
78 |
79 |
80 |
81 | Reusability: We can easily reuse the presentational components
82 | throughout our application for different purposes.
83 |
84 |
85 |
86 | Flexibility: Since presentational components don't alter the
87 | application logic, the appearance of presentational components can easily be
88 | altered by someone without knowledge of the codebase, for example a designer.
89 | If the presentational component was reused in many parts of the application,
90 | the change can be consistent throughout the app.
91 |
92 |
93 |
94 | Testing: Testing presentational components is easy, as they are usually
95 | pure functions. We know what the components will render based on which data we
96 | pass, without having to mock a data store.
97 |
98 |
99 |
100 | Not necessary with Hooks: Hooks make it possible to achieve the same result without having to use the Container/Presentational pattern.
101 |
102 | For example, we can use SWR to easily fetch data and conditionally render the listings or the skeleton component. Since we can use hooks in functional components and keep the code small and maintainable, we don't have to split the component into a container and presentational component.
103 |
104 | ```js
105 | import React from "react";
106 | import useSWR from "swr";
107 | import { LoadingListings, Listing, ListingsGrid } from "../components";
108 |
109 | function Listings(props) {
110 | const {
111 | data: listings,
112 | loading,
113 | error,
114 | } = useSWR("https://my.cms.com/listings", (url) =>
115 | fetch(url).then((r) => r.json())
116 | );
117 |
118 | if (loading) {
119 | return ;
120 | }
121 |
122 | return (
123 |
124 | {listings.map((listing) => (
125 |
126 | ))}
127 |
128 | );
129 | }
130 | ```
131 |
132 |
133 |
134 |
135 | Overkill: The Container/Presentational pattern can easily be an
136 | overkill in smaller sized application.
137 |
138 |
139 | ---
140 |
141 | ## Exercise
142 |
143 | We have a `Listings` component that both contains the fetching logic, as well as the rendering logic. In order to make the fetching logic more reusable, we can split this component up into a container and presentational component.
144 |
145 | #### Challenge
146 |
147 | Remove the data fetching logic from the `Listings` component, and move this to a separate `ListingsContainer` component located in `/components/container/Listings.tsx`
148 |
149 |
154 |
155 | #### Solution
156 |
157 |
162 |
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/higher-order-component.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Higher-Order Components
5 |
6 | Pass reusable logic down as props to components throughout your application
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | Higher-Order Components (HOC) make it easy to pass logic to components by wrapping them.
13 |
14 | For example, if we wanted to easily change the styles of a text by making the font larger and the font weight bolder, we could create two Higher-Order Components:
15 |
16 | - `withLargeFontSize`, which appends the `font-size: "90px"` field to the `style` attribute.
17 | - `withBoldFontWeight`, which appends the `font-weight: "bold"` field to the `style` attribute.
18 |
19 |
31 |
32 | Any component that's wrapped with either of these higher-order components will get a larger font size, a bolder font weight, or both!
33 |
34 | ---
35 |
36 | ### Implementation
37 |
38 | We can apply logic to another component, by:
39 |
40 | 1. Receiving another component as its `props`
41 | 2. Applying additional logic to the passed component
42 | 3. Returning the same or a new component with additional logic
43 |
44 | 
45 |
46 | To implement the above example, we can create a `withStyles` HOC that adds a `color` and `font-size` prop to the component's style.
47 |
48 | ```js
49 | export function withStyles(Component) {
50 | return (props) => {
51 | const style = {
52 | color: "red",
53 | fontSize: "1em",
54 | // Merge props
55 | ...props.style,
56 | };
57 |
58 | return ;
59 | };
60 | }
61 | ```
62 |
63 | We can import the `withStyles` HOC, and wrap any component that needs styling.
64 |
65 | ```js
66 | import { withStyles } from "./hoc/withStyles";
67 |
68 | const Text = () =>
Hello world!
;
69 | const StyledText = withStyles(Text);
70 | ```
71 |
72 |
73 | If you have a component that always needs to be wrapped within a HOC, you can also directly pass it instead of creating two separate components like we did in the example above.
74 |
75 | ```js
76 | const Text = withStyles(() => (
77 |
Hello world!
78 | ));
79 | ```
80 |
81 |
82 |
83 | ---
84 |
85 | ### Tradeoffs
86 |
87 |
88 | Separation of concerns: Using the Higher-Order Component pattern allows
89 | us to keep logic that we want to re-use all in one place. This reduces the
90 | risk of accidentally spreading bugs throughout the application by duplicating
91 | code over and over, potentially introducing new bugs each time
92 |
93 |
94 |
95 | Naming collisions: It can easily happen that the HOC overrides a prop of a component. Make sure that the HOC can handle accidental name collision, by either renaming the prop or merging the props.
96 |
97 | ```js
98 | function withStyles(Component) {
99 | return props => {
100 | const style = {
101 | padding: '0.2rem',
102 | margin: '1rem',
103 | // Merge props
104 | ...props.style
105 | }
106 |
107 | return
108 | }
109 | }
110 |
111 | // The `Button` component has a `style` prop, that shouldn't get overwritten in the HOC.
112 | const Button = () =
113 | const StyledButton = withStyles(Button)
114 | ```
115 |
116 |
117 |
118 |
119 | Readability: When using multiple composed HOCs that all pass props to
120 | the element that's wrapped within them, it can be difficult to figure out
121 | which HOC is responsible for which prop. This can hinder debugging and scaling
122 | an application easily.
123 |
124 |
125 | ---
126 |
127 | ### Exercise
128 |
129 | If we have a lot of components that fetch data, we might want to show a certain loader while they're still loading data.
130 |
131 | In that case, we might want to create a `withLoader` HOC, which returns a component that fetches data, and returns a `LoadingSpinner` component while it's fetching data.
132 |
133 | #### Challenge
134 |
135 | Complete the `withLoader` HOC, that shows a spinner when a component is still loading data.
136 |
137 |
138 |
139 | #### Solution
140 |
141 |
142 |
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/hooks-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Hooks Pattern
5 |
6 | Use functions to reuse stateful logic among multiple components throughout the app
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | React Hooks are functions special types of functions that you can use in order to:
13 |
14 | - Add state to a functional component
15 | - Reuse stateful logic among multiple components throughout the app.
16 | - Manage a component's lifecycle
17 |
18 | Besides built-in hooks, such as `useState`, `useEffect`, and `useReducer`, we can create custom hooks to easiliy share stateful logic across multiple components within the application.
19 |
20 | ---
21 |
22 | For example, if we wanted to see if a certain component is currently being hovered, we can create and use a `useHover` hook.
23 |
24 |
36 |
37 | We _could_ just add this logic to the `Listing` component itself. However, in order to make the hover logic reusable throughout the application, it makes more sense to create its own hook for it.
38 |
39 |
51 |
52 | Now that it's a hook, we can reuse the same logic throughout multiple components in our application. For example, if we also wnated to add the hover logic to the `Image` and `Button` component, we can simply use the hook within these functional components.
53 |
54 |
55 |
56 | ---
57 |
58 | ### Implementation
59 |
60 | React knows a hook is a hook when the name starts with `use`. Since this hook keeps track of the hovering state, it makes sense to name it `useHover`.
61 |
62 | Within this hook, we have to keep track of the hovering state by using three build-in hooks:
63 |
64 | 1. `useState`, which keeps track of whether the component is currently being hovered.
65 | 2. `useRef`, which creates a ref to the component that we're tracking.
66 | 3. `useEffect`, which gets executed as soon as the `ref` has a value. In here, we can add event listeners to the component to keep track of the hovering state.
67 |
68 | ```js
69 | export function useHover() {
70 | const [isHovering, setIsHovering] = React.useState(false);
71 | const ref = React.useRef(null);
72 |
73 | const handleMouseOver = () => setIsHovering(true);
74 | const handleMouseOut = () => setIsHovering(false);
75 |
76 | React.useEffect(() => {
77 | const node = ref.current;
78 | if (node) {
79 | node.addEventListener("mouseover", handleMouseOver);
80 | node.addEventListener("mouseout", handleMouseOut);
81 | return () => {
82 | node.removeEventListener("mouseover", handleMouseOver);
83 | node.removeEventListener("mouseout", handleMouseOut);
84 | };
85 | }
86 | }, [ref.current]);
87 |
88 | return [ref, isHovering];
89 | }
90 | ```
91 |
92 | We can use this hook in any component that cares about the hovering state.
93 |
94 | ```js
95 | import { useHover } from "../hooks/useHover";
96 |
97 | export function Listing() {
98 | const [ref, isHovering] = useHover();
99 |
100 | React.useEffect(() => {
101 | if (isHovering) {
102 | // Add logic here
103 | }
104 | }, [isHovering]);
105 |
106 | return (
107 |
108 |
109 |
110 | );
111 | }
112 | ```
113 |
114 | ---
115 |
116 | ### Tradeoffs
117 |
118 |
119 | Simplifies components: Hooks make it easy to add state to functional
120 | components, rather than (usually more complex) class components.
121 |
122 |
123 |
124 | Reusing stateful logic: Hooks allow you to reuse stateful logic among
125 | multiple components across the application, which reduces the chances of
126 | errors, and allows for composition with plain functions.
127 |
128 |
129 |
130 | Sharing non-visual logic: Hooks make it easy to share non-visual logic,
131 | without having to use patterns like HOC or Render Props
132 |
133 |
134 |
135 | Good alternative to older React design patterns: The hooks pattern is a good alternative to an older React design pattern, which is mainly used with Class components, namely the Presentational/Container pattern.
136 |
137 | 
138 |
139 | With Hooks, we no longer have to wrap Presentational components in Container components to pass data. Instead, we can directly use the Hook within the presentational component.
140 |
141 | 
142 |
143 |
144 |
145 |
146 | Rules of Hooks: Hooks require certain rules to be followed. without a
147 | linter plugin, it is difficult to know which rule has been broken, and you can
148 | accidentally end up using the wrong built-in hook.
149 |
150 |
151 | ---
152 |
153 | ### Exercise
154 |
155 | #### Challenge
156 |
157 | The code below is using the Container/Presentational pattern to display the listings. Refactor this code so that it uses a hook instead of a container component.
158 |
159 |
160 |
161 | #### Solution
162 |
163 |
164 |
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "conpres": "1. Con/Pres Pattern",
3 | "higher-order-component": "2. HOC Pattern",
4 | "render-props": "3. Render Props Pattern",
5 | "hooks-pattern": "4. Hooks Pattern",
6 | "provider-pattern": "5. Provider Pattern",
7 | "compound-pattern": "6. Compound Pattern"
8 | }
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/provider-pattern.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Provider Pattern
5 |
6 | Make data available to multiple child components
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | The Provider Pattern uses React's `Context` API - which is a way to easily share data between components.
13 |
14 | Let's say that we want to add a theme toggle to our landing page, where users can switch between light mode and dark mode.
15 |
16 |
22 |
23 | Several components change their style based on the currently active theme, such as the `TopNav`, the `Listing` cards, the `Main` section, and the `Toggle`.
24 |
25 | With the Provider pattern, we can share the theme state across multiple components throughout our application. The provider _provides_ this context to components, which in turn _consume_ this data.
26 |
27 |
33 |
34 | ---
35 |
36 | ##### Prop-drilling
37 |
38 | Before the Context API was available, we often ended up we often end up with something called _prop drilling_ when we wanted to share data across multiple components. This is the case when we pass props far down the component tree.
39 |
40 | 
41 |
42 | However, passing props down this way can get quite insecure and complex, and it's simply not a very scalable approach. We cannot easily rename a `prop`, or restructure the component tree. It can also easily lead to a decreased performance, since all child components need to re-render on a state update, even if they aren't consuming that data.
43 |
44 | The provider pattern solves this by exposing the values of the `Context` to all children within a `Provder`. A component can optionally consume this data, making it possible to pass data to multiple components without prop drilling.
45 |
46 |
52 |
53 | Only the components that care about the data get re-rendered when the state updates.
54 |
55 | ---
56 |
57 | ### Implementation
58 |
59 | A `Provider` is a higher order component provided to us by the `Context` object. We can create a `Context` object, using the `createContext` method that React provides for us.
60 |
61 | ```js
62 | export const ThemeContext = React.createContext(null);
63 |
64 | export function ThemeProvider({ children }) {
65 | const [theme, setTheme] = React.useState("light");
66 |
67 | return (
68 |
69 | {children}
70 |
71 | );
72 | }
73 | ```
74 |
75 | Any component wrapped in the `ThemeProvider` now has access to the `theme` and `setTheme` properties.
76 |
77 | ```js
78 | import { ThemeProvider, ThemeContext } from "../context";
79 |
80 | const LandingPage = () => {
81 |
82 |
83 |
84 | ;
85 | };
86 |
87 | const TopNav = () => {
88 | return (
89 |
90 | {{ theme }} =>{" "}
91 |
92 | ...
93 |
{" "}
94 | }
95 |
96 | );
97 | };
98 |
99 | const Toggle = () => {
100 | return (
101 |
102 | {{ theme, setTheme }} => (
103 |
112 | )}
113 |
114 | );
115 | };
116 | ```
117 |
118 | However, we can also combine the Provider with the Hooks pattern. Instead of wrapping components in a `` component, we can use the built-in `useContext` hook.
119 |
120 | ```js
121 | export const ThemeContext = React.createContext(null);
122 |
123 | export function useThemeContext() {
124 | const { theme, setTheme } = useContext(ThemeContext);
125 |
126 | return { theme, setTheme };
127 | }
128 |
129 | export function ThemeProvider({ children }) {
130 | const [theme, setTheme] = React.useState("light");
131 |
132 | return (
133 |
134 | {children}
135 |
136 | );
137 | }
138 | ```
139 |
140 | Each component that needs to have access to the `ThemeContext`, can now simply use the `useThemeContext` hook.
141 |
142 | ```js
143 | import { useThemeContext } from "../context";
144 |
145 | const LandingPage = () => {
146 |
147 |
148 |
149 | ;
150 | };
151 |
152 | const TopNav = () => {
153 | const { theme } = useThemeContext();
154 | return (
155 |
156 | ...
157 |
158 | );
159 | };
160 |
161 | const Toggle = () => {
162 | const { theme, setTheme } = useThemeContext();
163 | return (
164 |
173 | );
174 | };
175 | ```
176 |
177 | By creating hooks for the different contexts, it's easy to separate the providers's logic from the components that render the data.
178 |
179 | ---
180 |
181 | ### Tradeoffs
182 |
183 |
184 | Scalability: There's less risk involved when sharing state across
185 | multiple components with the Provider Pattern, as we can easily rename values
186 | when our application grows, and easily reuse components.
187 |
188 |
189 |
190 | Performance: Components that consume the `Provider`'s context re-render
191 | whenever a value changes. This can cause performance issues If you aren't
192 | careful which components are consuming the context.
193 |
194 |
195 | ---
196 |
197 | ### Exercise
198 |
199 | #### Challenge
200 |
201 | The application below contains a `Listings` component and an `Input` component that both use the `useListings` hook. However, this results in two separate calls to the API. Refactor this code so that it uses a `ListingsProvider` that provides the listings data to both components.
202 |
203 |
204 |
205 | #### Solution
206 |
207 |
208 |
--------------------------------------------------------------------------------
/pages/patterns/react-patterns/render-props.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Render Props Pattern
5 |
6 | Pass JSX elements to components through props
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | With the Render Props pattern, we pass components as props to other components. The components that are passed as props can in turn receive props from that component.
13 |
14 | Render props make it easy to reuse logic across multiple components.
15 |
16 |
22 |
23 | ---
24 |
25 | ### Implementation
26 |
27 | If we wanted to implement an `input` field with which a user can convert a temperature from Celsius to Kelvin and Fahrenheit, we can use the `renderKelvin` and `renderFahrenheit` render props.
28 |
29 | These props receive the `value` of the input, which they convert to the correct temperature in either K or °F.
30 |
31 | ```js
32 | function Input(props) {
33 | const [value, setValue] = useState("");
34 |
35 | return (
36 | <>
37 | setValue(e.target.value)} />
38 | {props.renderKelvin({ value: value + 273.15 })}
39 | {props.renderFahrenheit({ value: (value * 9) / 5 + 32 })}
40 | >
41 | );
42 | }
43 |
44 | export default function App() {
45 | return (
46 |
{value}K
}
48 | renderFahrenheit={({ value }) =>
{value}°F
}
49 | />
50 | );
51 | }
52 | ```
53 |
54 | ---
55 |
56 | ### Tradeoffs
57 |
58 |
59 | Reusability: Since render props can be different each time, we can make
60 | components that receive render props highly reusable for multiple usecases.
61 |
62 |
63 |
64 | Separation of concerns: We can separate our app's logic from rendering
65 | components through render props. The stateful component that receives a render
66 | prop can pass the data onto stateless components, which merely render the
67 | data.
68 |
69 |
70 |
71 | Solution to HOC problems: Since we explicitly pass props, we solve the
72 | HOC's implicit props issue. The props that should get passed down to the
73 | element, are all visible in the render prop's arguments list. We know exactly
74 | where certain props come from.
75 |
76 |
77 |
78 | Unnecessary with Hooks: Hooks changed the way we can add reusability
79 | and data sharing to components, which can replace the render props pattern in
80 | many cases.
81 |
82 |
83 | ---
84 |
85 | ### Exercise
86 |
87 | #### Challenge
88 |
89 | Refactor the following code so that `TemperatureConverter` uses the `renderKelvin` and `renderFahrenheit` props to render the `` and `` components.
90 |
91 | {" "}
92 |
93 | #### Solution
94 |
95 | {" "}
96 |
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/client-side-rendering.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 |
3 | # Client-Side Rendering
4 |
5 | Render your application's UI on the client
6 |
7 | ---
8 |
9 | ### Overview
10 |
11 | The contents of a client-side rendered application get rendered in the browser.
12 |
13 |
19 |
20 | When a user requests a client-side rendered application, the server initially responds with the barebones HTML file.
21 |
22 | Once the client recieves this HTML file, the HTML parser parses the content, and fetches the JavaScript bundle when it reaches the `script` tag.
23 |
24 | When the client has downloaded the JavaScript, it executes its contents. This contains DOM methods to dynamically append content to the DOM tree, which results in rendered content to the user's screen.
25 |
26 | ---
27 |
28 | ### Implementation
29 |
30 | A basic client-side application consists of at least two files.
31 |
32 | First, we need to have a barebones HTML file, which contains the element that the JavaScript file can use to dynamically append content to.
33 |
34 | ```html
35 |
36 |
37 |
38 |
39 |
40 |
41 | ```
42 |
43 | We also need a JavaScript file, which contains methods to update the DOM tree and dynamically render data. This file gets fetched after (or during) the HTML parsing.
44 |
45 | ```js
46 | const root = document.getElementById("root");
47 |
48 | // DOM manipulation
49 | root.appendChild(...)
50 | ```
51 |
52 | ---
53 |
54 | ### Tradeoffs
55 |
56 |
57 | Performance
58 |
59 | 
60 |
61 | - **TTFB**: The Time To First Byte can be fast, since the initial HTML does not contain large components.
62 | - **FCP**: The First Contentful Paint can occur once the JavaScript bundle has downloaded, parsed, and executed its contents.
63 | - **TTI**: The Time To Interactive can occur once the JavaScript bundle has downloaded, parsed, and executed its contents to bind the event handlers to the components.
64 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
65 |
66 |
67 |
68 | ---
69 |
70 |
71 | Interactivity: The rendered content is instantly interactive. The
72 | fetched JavaScript bundle can directly add Event Listeners to the DOM nodes.
73 | As opposed to other rendering patterns, users are never left with a visible
74 | but non-interactive page.
75 |
76 |
77 |
78 | Single server roundtrip: The entire web application is loaded on the
79 | first request. As the user navigates by clicking on links, no new request is
80 | generated to the server to render these pages.
81 |
82 |
83 |
84 | Bundle size: Modern applications, which usually contain multiple pages,
85 | can easily have large JavaScript bundles. The larger the bundle, the longer it
86 | takes to download and execute the JavaScript before the first content is
87 | visible and interactive.
88 |
89 |
90 |
91 | SEO: Large bundles and a waterfall of API requests may result in
92 | content not being rendered fast enough for a crawler to index it. Workarounds
93 | are required to make a client-rendered website SEO friendly, and work around
94 | the limitations of a crawler's ability to understand JavaScript.
95 |
96 |
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/incremental-static-regeneration.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 |
3 | # Incremental Static Regeneration
4 |
5 | Pre-render certain pages, and render the other pages on-demand
6 |
7 | ---
8 |
9 | ### Overview
10 |
11 | Static rendering comes with many performance benefits, but it can end up in long build times if we have many pages to pre-render. We can also only update the content of our page by redeploying the website, which isn't a great user experience.
12 |
13 | Incremental Static Generation allows us to only pre-render a subset of pages, for example pages that are likely to be requested by the user, and render the rest on-demand. When the user requests a page that hasn't been pre-rendered yet, the page gets server-rendered, after which it can get cached by a CDN.
14 |
15 |
27 |
28 | Besides only pre-rendering a subset of pages, we can also automatically invalidate cached pages based on a `stale-while-revalidate` approach. When a user requests a page that is stale - meaning it's been in cache for longer than the provided number that it should be cached for - a regeneration is triggered in the background. While this is happening, the user gets to see the stale page, but they're able to see the updated content on subsequent requests.
29 |
30 |
42 |
43 | ---
44 |
45 | ### Implementation
46 |
47 | We can implement Incremental Static Regeneration using Next.js's `getStaticProps` method in combination with the `getStaticPaths` method.
48 |
49 | ```js
50 | import { Listings, ListingsSkeleton } from "../components";
51 |
52 | export default function Listing(props) {
53 | return
54 | }
55 |
56 | export async function getStaticProps(props) {
57 | const res = await fetch(`https://my.cms.com/listings/${props.params.id}`);
58 | const { listing } = await res.json();
59 |
60 | return { props: { listing } }
61 | }
62 |
63 | export function getStaticPaths() {
64 | const res = await fetch(`https://my.cms.com/listings?limit=20`);
65 | const { listings } = await res.json();
66 |
67 | return {
68 | params: listings.map(listing => ({ id: listing.id })),
69 | fallback: false
70 | }
71 | }
72 |
73 | ```
74 |
75 | ---
76 |
77 | ### Tradeoffs
78 |
79 |
80 | Performance
81 |
82 | 
83 |
84 | - **TTFB**: The Time To First Byte can be fast, since the initial HTML does not contain large components.
85 | - **FCP**: The First Contentful Paint can occur once the HTML has been parsed and rendered.
86 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
87 | - **TTI**: The Time To Interactive can occur once the HTML has been rendered, and the JavaScript bundle has been downloaded, parsed, and executed its contents to bind the event handlers to the components.
88 |
89 |
90 |
91 | ---
92 |
93 |
94 | Static benefits: Since we're still statically generating the HTML
95 | files, we can benefit from all the benefit that Plain Static Rendering
96 | provided, such as cacheability, a good SEO, a high availability, and a low
97 | backend load.
98 |
99 |
100 |
101 | Server costs: It might happen that our data doesn't update every number
102 | of seconds, resulting in an unnecessary cache invalidation and page
103 | regeneration.
104 |
105 |
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/introduction.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 |
3 | # Rendering Patterns
4 |
5 | Rendering content on the web can be done in many ways. The decision _how_ and _where_ to fetch and render content is key to the performance of an application.
6 |
7 | The available frameworks and libraries can be used to implement different rendering patterns like Client-Side Rendering, Static Rendering, Incremental Static Regeneration, Progressive Rendering, Server-Side Rendering and many more. Understanding the tradeoffs and use cases for these patterns can drastically help the performance of your application, resulting in a great user and developer experience.
8 |
9 | ---
10 |
11 | ### Web Vitals
12 |
13 | To measure how well our website performs, we can use a set of useful measurements called Web Vitals. A subset of these measurements - the Core Web Vitals - is usually used to determine the performance of a page, and can affect your website's SEO.
14 |
15 |
16 |
17 |
18 |
19 |
26 | TTFB
27 |
28 |
29 |
30 | Time To First Byte
31 |
37 | Time it takes for a client to receive the first byte of page content
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
52 | FCP
53 |
54 |
55 |
56 | First Contentful Paint
57 |
63 | Time it takes the browser to render the first piece of content after
64 | navigation
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
79 | LCP
80 |
81 |
82 |
83 | Largest Contentful Paint
84 |
90 | Time it takes to load and render the page's main content
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
105 | TTI
106 |
107 |
108 |
109 | Time To Interactive
110 |
116 | Time from when the page starts loading to when it's reliably
117 | responding to user input quickly
118 |
169 | Time from when the user interacts with the page to the time when the
170 | event handlers are able to run
171 |
172 |
173 |
174 |
175 |
176 |
177 | ---
178 |
179 | The following terms will be useful when covering the different rendering techniques:
180 |
181 |
182 | - **Compiling**: Converting JavaScript into native machine code - **Execution
183 | time**: The time it takes to execute the previously fetched, parsed, and
184 | compiled data - **Hydration**: Attaching handlers to a DOM node whose HTML
185 | contents were server-rendered, making the component interactive - **Idle**:
186 | The browser's state when it's not performing any action - **Loading time**:
187 | The time it takes to fetch the data from the server - **Main thread**: The
188 | thread on which the browser executes all the JavaScript, performs layout,
189 | reflows, and garbage collection - **Parsing**: Converting an HTML source into
190 | DOM nodes, and generating an AST - **Processing**: Parsing, compiling, and
191 | executing the previously fetched data - **Processing time**: The time it takes
192 | to parse and compile the previously fetched data
193 |
194 |
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "introduction": "1. Introduction",
3 | "client-side-rendering": "2. Client-Side Rendering",
4 | "static-rendering": "3. Static Rendering",
5 | "incremental-static-regeneration": "4. Incremental Static Regeneration",
6 | "server-side-rendering": "5. Server-Side Rendering",
7 | "streaming-ssr": "6. Streaming SSR"
8 | }
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/server-side-rendering.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Server-Side Rendering
5 |
6 | Generate HTML on every request
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | With Server-Side rendering, we can generate HTML on the server (or serverless function) on every request.
13 |
14 |
20 |
21 | When a user requests a server-side rendered application, the server generates the HTML, and returns this to the client.
22 |
23 | The browser renders this content, which is initially just plain non-interactive HTML elements. To bind event listeners to the components, the client sends an additional request to fetch the JavaScript bundle to hydrate the components.
24 |
25 | ---
26 |
27 | ### Implementation
28 |
29 | When server-side rendering an application, we need a method to render HTML from our React components on the server, and hydrate the non-interactive HTML on the client. One way to render HTML on the server, is by using the `renderToString` method. This function returns an HTML string corresponding to the React element. The HTML can then be rendered to the client for a faster page load, and hydrated with the `hydrateRoot` method on the client.
30 |
31 |
37 |
38 | ---
39 |
40 | React frameworks such as Next.js, Remix and Astro make it easy to server-render (parts of) your application.
41 |
42 | When using Next.js, we can server-render a page by using the `getServerSideProps` method.
43 |
44 | ```js
45 | import { Listings, ListingsSkeleton } from "../components";
46 |
47 | export default function Home(props) {
48 | return ;
49 | }
50 |
51 | export async function getServerSideProps({ req, res }) {
52 | const res = await fetch("https://my.cms.com/listings");
53 | const listings = await res.json();
54 |
55 | return {
56 | props: { listings },
57 | };
58 | }
59 | ```
60 |
61 | This methods runs on every request, and passes the props to the page to be dynamically generated.
62 |
63 |
64 |
65 | ---
66 |
67 | ### Tradeoffs
68 |
69 |
70 | Performance
71 |
72 | 
73 |
74 | - **TTFB**: The TTFB can be slow, since the page still has to be generated on-demand.
75 | - **FCP**: The First Contentful Paint can occur once the HTML has been parsed and rendered.
76 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
77 | - **TTI**: The Time To Interactive can occur once the HTML has been rendered, and the JavaScript bundle has been downloaded, parsed, and executed its contents to bind the event handlers to the components.
78 |
79 |
80 |
81 | ---
82 |
83 |
84 | Personalized pages: Server-rendering is useful for pages that need
85 | request-based data, such as a user cookie.
86 |
87 |
88 |
89 | Render blocking: Server-rendering can block the generation of pages
90 | that are authentication-based.
91 |
92 |
93 |
94 | Initial load: Since the page still has to be generated when the user requests it, it can take a while before the user can see something rendered on their screens. To optimize SSR, you can:
95 |
96 | 1. Optimize database queries. If the distance between your server and database is long, it can take a while to establish a connection and retrieve data. Consider moving your database or server closer to each other.
97 | 2. Add `Cache-Control` headers to your responses.
98 |
99 |
100 |
101 |
102 | Availability: If your server or region goes down, so does your website.
103 |
104 |
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/static-rendering.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Static Rendering
5 |
6 | Deliver pre-rendered HTML content that was generated when the site was built
7 |
8 | ---
9 |
10 | ## Static Rendering
11 |
12 | ### Overview
13 |
14 | With static rendering, the HTML contents are pre-generated at build time.
15 |
16 |
22 |
23 | When a user requests a statically rendered application, the server responds with the HTML file.
24 |
25 | Once the client receives this HTML file, the HTML parser parses the content and renders the non-interactive content to the screen. If a `script` tag is present, the client sends an additional request to fetch this bundle.
26 |
27 | When the client has downloaded the JavaScript, it executes its contents and adds event listeners to the HTML elements to make them interactive.
28 |
29 | ---
30 |
31 | ### Implementation
32 |
33 | The bare minimum required for a static rendered application is to have one single HTML file that contains all the contents of the elements that need to be rendered to the screen.
34 |
35 | ```jsx
36 | const listings = [
37 | { id: 1, address: "..." },
38 | { id: 2, address: "..." },
39 | ];
40 |
41 | export default function Home() {
42 | return ;
43 | }
44 | ```
45 |
46 | There can also be an optional JavaScript file, which is only necessary if the components are interactive, to bind event listeners to the already rendered HTML elements.
47 |
48 |
49 |
50 | ---
51 |
52 | ### Tradeoffs
53 |
54 |
55 | Performance
56 |
57 | 
58 |
59 | - **TTFB**: The Time To First Byte can be fast, since the initial HTML does not contain large components.
60 | - **FCP**: The First Contentful Paint can occur once the JavaScript bundle has downloaded, parsed, and executed its contents.
61 | - **TTI**: The Time To Interactive can occur once the JavaScript bundle has downloaded, parsed, and executed its contents to bind the event handlers to the components.
62 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
63 |
64 |
65 |
66 | ---
67 |
68 |
69 | Cacheability: Pre-rendered HTML files can be cached and served by a
70 | global CDN. Users can benefit from quick responses when they request a page,
71 | as the request doesn't have to go all the way to the origin server.
72 |
73 |
74 |
75 | SEO: The HTML content can be rendered by web crawlers with no extra
76 | effort.
77 |
78 |
79 |
80 | Availability: Static is always online. Even if your backend or data
81 | source (e.g. database) goes down, your existing pre-rendered page will still
82 | be available.
83 |
84 |
85 |
86 | Backend load: With Static Generation, the database or API wouldn't need
87 | to be hit on every request. Page-rendering code wouldn't have to run on every
88 | request.
89 |
90 |
91 |
92 | Dynamic data: If a statically generated page needs dynamic content, for
93 | example from an external data source, it needs to fetch this client-side. This
94 | can result in a long LCP, and higher server costs.
95 |
96 |
97 | ---
98 |
99 | ## Dynamic data
100 |
101 | There are several ways to add dynamic data to statically rendered pages, the difference being _when_ this data is fetched.
102 |
103 | ### Fetch dynamic data at build time
104 |
105 | #### Overview
106 |
107 | We can fetch data server-side at build time, and generate the HTML based on the fetched data. When you're working with frameworks, they usually offer methods to easily add dynamic data to static pages.
108 |
109 | ---
110 |
111 | #### Implementation
112 |
113 | If you're using Next.js, you can do this with the `getStaticProps` method.
114 |
115 | ```js
116 | import { Listings, ListingsSkeleton } from "../components";
117 |
118 | export default function Home(props) {
119 | return ;
120 | }
121 |
122 | export async function getStaticProps() {
123 | const res = await fetch("https://my.cms.com/listings");
124 | const listings = await res.json();
125 |
126 | return { props: { listings } };
127 | }
128 | ```
129 |
130 | This method runs server-side at build time, generating HTML that contains the fetched data.
131 |
132 |
133 |
134 | ---
135 |
136 | #### Tradeoffs
137 |
138 |
139 | Performance
140 |
141 | 
142 |
143 | - **TTFB**: The Time To First Byte can be fast, since the initial HTML does not contain large components.
144 | - **FCP**: The First Contentful Paint can occur once the JavaScript bundle has downloaded, parsed, and executed its contents.
145 | - **TTI**: The Time To Interactive can occur once the JavaScript bundle has downloaded, parsed, and executed its contents to bind the event handlers to the components.
146 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
147 |
148 |
149 |
150 |
151 | Static benefits: Since we're still statically generating the HTML
152 | files, we can benefit from all the benefit that Plain Static Rendering
153 | provided, such as cacheability, a good SEO, a high availability, and a low
154 | backend load.
155 |
156 |
157 |
158 | Dynamic data: The `getStaticProps` method allows us to use dynamic
159 | data, and renew the data at build time.
160 |
161 |
162 |
163 | Redeployment needed to refresh data: Since the data only gets
164 | regenerated when the page is built, we'd have to redeploy the website to
165 | update the contents of the page.
166 |
167 |
168 |
169 | Long build times: If your applications contains many pages that need to
170 | be pre-rendered, this could end up in long build times.
171 |
172 |
173 | ---
174 |
175 | ### Fetch dynamic data client-side
176 |
177 | #### Overview
178 |
179 | One approach to add dynamic data to a staticaly rendered page, is to use a client-side `fetch`. This is usually a great pattern for pages that contain data that should be updated on every request.
180 |
181 |
187 |
188 | ---
189 |
190 | #### Implementation
191 |
192 | We can use SWR to fetch the data client-side, and show a skeleton component while the data is being fetched.
193 |
194 | ```jsx
195 | import useSWR from "swr";
196 | import { Listings, ListingsSkeleton } from "../components";
197 |
198 | export default function Home() {
199 | const { data, loading } = useSWR("/api/listings", (...args) =>
200 | fetch(...args).then((res) => res.json())
201 | );
202 |
203 | if (loading) {
204 | return ;
205 | }
206 |
207 | return ;
208 | }
209 | ```
210 |
211 |
212 |
213 | ---
214 |
215 | #### Tradeoffs
216 |
217 |
218 | Performance
219 |
220 | 
221 |
222 | - **TTFB**: The Time To First Byte can be fast, since the initial HTML does not contain large components.
223 | - **FCP**: The First Contentful Paint can occur once the HTML has been parsed and rendered.
224 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
225 | - **TTI**: The Time To Interactive can occur once the HTML has been rendered, and the JavaScript bundle has been downloaded, parsed, and executed its contents to bind the event handlers to the components.
226 |
227 |
228 |
229 | ---
230 |
231 |
232 | Static benefits: Since we're still statically generating the HTML
233 | files, we can benefit from all the benefit that Plain Static Rendering
234 | provided, such as cacheability, a good SEO, a high availability, and a low
235 | backend load.
236 |
237 |
238 |
239 | Server costs: The data gets requested on every page load, which could
240 | result in a higher server costs.
241 |
242 |
243 |
244 | Layout shift: A layout shift can occur if the skeleton components don't
245 | match the rendered components sizes.
246 |
247 |
--------------------------------------------------------------------------------
/pages/patterns/rendering-patterns/streaming-ssr.mdx:
--------------------------------------------------------------------------------
1 | import Callout from "../../../components/Callout";
2 | import StackBlitz from "../../../components/StackBlitz";
3 |
4 | # Streaming Server-Side Rendering
5 |
6 | Generate HTML on every request, sending it down piece by piece
7 |
8 | ---
9 |
10 | ### Overview
11 |
12 | Streaming Server-Side rendering allows us to send components down to the client as soon as they've been generated.
13 |
14 | With regular Server-Side rendering, the user has to wait for the entire HTML to be generated on the server before it gets send down to the client. Before hydration could begin, the entire bundle had to be downloaded and executed.
15 |
16 |
22 |
23 | However, with streaming server-side rendering, the components get streamed down as soon as they're ready.
24 |
25 |
31 |
32 | ---
33 |
34 | ### Implementation
35 |
36 | ---
37 |
38 | ### Tradeoffs
39 |
40 |
41 | Performance
42 |
43 | 
44 |
45 | - **TTFB**: The TTFB can be slow, since the page still has to be generated on-demand.
46 | - **FCP**: The First Contentful Paint can occur once the HTML has been parsed and rendered.
47 | - **LCP**: The Largest Contentful Paint can occur at the same time as the First Contentful Paint, provided there aren't any large components such as large images or videos.
48 | - **TTI**: The Time To Interactive can occur once the HTML has been rendered, and the JavaScript bundle has been downloaded, parsed, and executed its contents to bind the event handlers to the components.
49 |
50 |
51 |
52 | ---
53 |
54 |
55 | Performance regardless of page size: With Streaming SSR, we can have a
56 | fast TTFB since the first byte reaches the client soon after rendering starts
57 | on the server. This results in a good TTFB for any page of any size.
58 |
59 |
60 |
61 | Network Backpressure: If the network is clogged and not able to
62 | transfer any more bytes, the renderer gets a signal and stops streaming till
63 | the network is cleared up. Thus, the server uses less memory and is more
64 | responsive to I/O conditions. This enables your Node.js server to render
65 | multiple requests at the same time and prevents heavier requests from blocking
66 | lighter requests for a long time. As a result, the site stays responsive even
67 | in challenging conditions.
68 |
69 |
70 |
71 | Support: Not all runtimes support HTTP streaming, which is necessary
72 | for streaming SSR.
73 |
74 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/.DS_Store
--------------------------------------------------------------------------------
/public/design-patterns/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/.DS_Store
--------------------------------------------------------------------------------
/public/design-patterns/module-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/module-pattern/1.png
--------------------------------------------------------------------------------
/public/design-patterns/prototype-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/prototype-pattern/1.png
--------------------------------------------------------------------------------
/public/design-patterns/prototype-pattern/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/prototype-pattern/2.png
--------------------------------------------------------------------------------
/public/design-patterns/prototype-pattern/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/prototype-pattern/3.png
--------------------------------------------------------------------------------
/public/design-patterns/prototype-pattern/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/prototype-pattern/4.png
--------------------------------------------------------------------------------
/public/design-patterns/proxy-pattern/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/proxy-pattern/001.png
--------------------------------------------------------------------------------
/public/design-patterns/singleton-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/design-patterns/singleton-pattern/1.png
--------------------------------------------------------------------------------
/public/fm-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/fm-logo.png
--------------------------------------------------------------------------------
/public/javascript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/js.png
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/og-image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/og-image2.png
--------------------------------------------------------------------------------
/public/ogimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/ogimage.png
--------------------------------------------------------------------------------
/public/ogimage1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/ogimage1.png
--------------------------------------------------------------------------------
/public/performance-patterns/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/performance-patterns/.DS_Store
--------------------------------------------------------------------------------
/public/performance-patterns/browser-hints/prefetch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/performance-patterns/browser-hints/prefetch.png
--------------------------------------------------------------------------------
/public/performance-patterns/browser-hints/preload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/performance-patterns/browser-hints/preload.png
--------------------------------------------------------------------------------
/public/performance-patterns/introduction/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/performance-patterns/introduction/1.png
--------------------------------------------------------------------------------
/public/performance-patterns/introduction/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/performance-patterns/introduction/2.png
--------------------------------------------------------------------------------
/public/performance-patterns/introduction/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/performance-patterns/introduction/3.png
--------------------------------------------------------------------------------
/public/react-state-patterns/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/.DS_Store
--------------------------------------------------------------------------------
/public/react-state-patterns/compound-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/compound-pattern/1.png
--------------------------------------------------------------------------------
/public/react-state-patterns/hoc-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/hoc-pattern/1.png
--------------------------------------------------------------------------------
/public/react-state-patterns/hooks-pattern/prescon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/hooks-pattern/prescon.png
--------------------------------------------------------------------------------
/public/react-state-patterns/hooks-pattern/prescon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/hooks-pattern/prescon2.png
--------------------------------------------------------------------------------
/public/react-state-patterns/provider-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/provider-pattern/1.png
--------------------------------------------------------------------------------
/public/react-state-patterns/render-props-pattern/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/react-state-patterns/render-props-pattern/1.png
--------------------------------------------------------------------------------
/public/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/rendering-patterns/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/.DS_Store
--------------------------------------------------------------------------------
/public/rendering-patterns/client-side-rendering/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/client-side-rendering/1.png
--------------------------------------------------------------------------------
/public/rendering-patterns/server-side-rendering/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/server-side-rendering/1.png
--------------------------------------------------------------------------------
/public/rendering-patterns/static-rendering/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/static-rendering/.DS_Store
--------------------------------------------------------------------------------
/public/rendering-patterns/static-rendering/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/static-rendering/1.png
--------------------------------------------------------------------------------
/public/rendering-patterns/static-rendering/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/static-rendering/2.png
--------------------------------------------------------------------------------
/public/rendering-patterns/streaming-ssr/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lydiahallie/javascript-react-patterns/4ec96d8a6afaa1a8cd1b805cbbeee490f32623b1/public/rendering-patterns/streaming-ssr/1.png
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind utilities;
2 |
3 | body {
4 | background: linear-gradient(
5 | to bottom,
6 | rgba(255, 255, 255, 0) 0%,
7 | rgba(255, 255, 255, 1) 300px
8 | ),
9 | fixed 0 0 / 20px 20px radial-gradient(#d1d1d1 1px, transparent 0),
10 | fixed 10px 10px / 20px 20px radial-gradient(#d1d1d1 1px, transparent 0);
11 | }
12 |
13 | .dark body {
14 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, #000 300px),
15 | fixed 0 0 / 20px 20px radial-gradient(#313131 1px, transparent 0),
16 | fixed 10px 10px / 20px 20px radial-gradient(#313131 1px, transparent 0);
17 | }
18 |
19 | pre::-webkit-scrollbar {
20 | display: none;
21 | }
22 |
23 | pre {
24 | -ms-overflow-style: none;
25 | scrollbar-width: none;
26 | }
27 |
28 | .dark .nextra-container .nextra-sidebar ul li.active > a {
29 | color: #fff7ba !important;
30 | background: #f7df1e1c !important;
31 | }
32 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = require("tailwindcss/colors");
2 |
3 | module.exports = {
4 | content: [
5 | "./components/**/*.tsx",
6 | "./nextra-theme-docs/**/*.tsx",
7 | "./nextra-theme-docs/**/*.css",
8 | "./pages/**/*.md",
9 | "./pages/**/*.mdx",
10 | "./pages/**/*.tsx",
11 | "./pages/**/*.js",
12 | "./theme.config.js",
13 | "./styles.css",
14 | ],
15 | theme: {
16 | extend: {
17 | fontFamily: {
18 | sans: [`"Inter"`, "sans-serif"],
19 | mono: [
20 | "Menlo",
21 | "Monaco",
22 | "Lucida Console",
23 | "Liberation Mono",
24 | "DejaVu Sans Mono",
25 | "Bitstream Vera Sans Mono",
26 | "Courier New",
27 | "monospace",
28 | ],
29 | },
30 | colors: {
31 | dark: "#000",
32 | gray: colors.neutral,
33 | blue: colors.blue,
34 | orange: colors.orange,
35 | green: colors.green,
36 | red: colors.red,
37 | yellow: colors.yellow,
38 | },
39 | screens: {
40 | sm: "640px",
41 | md: "768px",
42 | lg: "1024px",
43 | betterhover: { raw: "(hover: hover)" },
44 | },
45 | },
46 | },
47 | darkMode: "class",
48 | };
49 |
--------------------------------------------------------------------------------
/theme.config.js:
--------------------------------------------------------------------------------
1 | const theme = {
2 | titleSuffix: " | JavaScript Patterns",
3 | search: true,
4 | unstable_flexsearch: true,
5 | unstable_staticImage: true,
6 | floatTOC: true,
7 | font: false,
8 | github: "https://github.com/lydiahallie/javascript-react-patterns",
9 | projectLink: "https://github.com/lydiahallie/javascript-react-patterns",
10 | logo: () => (
11 | <>
12 |
18 |