-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.html
438 lines (424 loc) · 23.6 KB
/
README.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Advanced React in 2023</title>
<style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only] {
display: none;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
font-size: 14px;
line-height: 1.6;
}
</style>
<style>
.task-list-item {
list-style-type: none;
}
.task-list-item-checkbox {
margin-left: -20px;
vertical-align: middle;
pointer-events: none;
}
</style>
</head>
<body class="vscode-body vscode-light">
<h1 id="advanced-react-in-2023">Advanced React in 2023</h1>
<ul>
<li><a href="#introduction">Introduction</a>
<ul>
<li><a href="#who-am-i">Who am I?</a></li>
<li><a href="#who-are-you">Who are you?</a></li>
<li><a href="#before-we-start">Before we start</a></li>
</ul>
</li>
<li><a href="#repository-setup">Repository setup</a></li>
<li><a href="#topics">Topics</a>
<ul>
<li><a href="#typescript-in-react">TypeScript in React</a>
<ul>
<li><a href="#-01-first-steps-with-typescript">💪 01 First steps with TypeScript</a></li>
</ul>
</li>
<li><a href="#relearn-the-principles-of-react">Relearn the principles of React</a>
<ul>
<li><a href="#the-rules-of-react">The rules of React</a>
<ul>
<li><a href="#-02-fix-the-rule-breaks">💪 02 Fix the rule breaks</a></li>
</ul>
</li>
<li><a href="#state-in-react">State in React</a>
<ul>
<li><a href="#-03-impossible-state">💪 03 Impossible state</a></li>
<li><a href="#-04-explicit-states">💪 04 Explicit states</a></li>
</ul>
</li>
<li><a href="#useeffect-usememo--usecallback">useEffect, useMemo & useCallback</a>
<ul>
<li><a href="#-05-unnecessary-useeffects">💪 05 Unnecessary useEffects</a></li>
<li><a href="#-06-sync-with-the-outside-world">💪 06 Sync with the outside world</a></li>
<li><a href="#-07-fighting-dependencies">💪 07 Fighting dependencies</a></li>
</ul>
</li>
<li><a href="#imperative-apis-in-react">Imperative APIs in React</a>
<ul>
<li><a href="#-08-imperative-apis">💪 08 Imperative APIs</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#component-patterns">Component Patterns</a>
<ul>
<li><a href="#the-children-prop-or-jsx-as-props">The children prop or JSX as props</a>
<ul>
<li><a href="#-09-layout-components">💪 09 Layout-components</a></li>
</ul>
</li>
<li><a href="#render-props">Render Props</a>
<ul>
<li><a href="#-10-render-props">💪 10 Render props</a></li>
</ul>
</li>
<li><a href="#compound-components">Compound Components</a>
<ul>
<li><a href="#-11-compound-components">💪 11 Compound components</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#additional-topics">Additional topics</a>
<ul>
<li><a href="#security-in-react">Security in React</a></li>
<li><a href="#-12-testing-in-react-applications">💪 12 Testing in React applications</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="introduction">Introduction</h2>
<h3 id="who-am-i">Who am I?</h3>
<ul>
<li>Andreas</li>
<li>Developer, Trainer, Speaker</li>
<li>Web technologies with main focus on TypeScript & React</li>
</ul>
<h3 id="who-are-you">Who are you?</h3>
<ul>
<li>Name</li>
<li>Experience</li>
<li>Expectations for the workshop</li>
</ul>
<h3 id="before-we-start">Before we start</h3>
<ul>
<li>Be curious</li>
<li>Be open</li>
</ul>
<h2 id="repository-setup">Repository setup</h2>
<ul>
<li>Bundler/dev server: <a href="https://vitejs.dev/">Vite</a></li>
<li>Styles: <a href="https://tailwindcss.com/">Tailwind CSS</a></li>
<li>Tools:
<ul>
<li><a href="https://www.typescriptlang.org/">TypeScript</a></li>
<li><a href="https://prettier.io/">Prettier</a></li>
<li><a href="https://eslint.org/">eslint</a> - No checking of codestyle, formatting, and anything TypeScript already checks</li>
</ul>
</li>
<li>Testing:
<ul>
<li><a href="https://vitest.dev/">Vitest</a> - like jest but integrates with vite and is much faster</li>
<li><a href="https://testing-library.com/docs/react-testing-library/intro/">React Testing library</a></li>
<li><a href="https://mswjs.io/">Mock Service Worker (msw)</a></li>
</ul>
</li>
</ul>
<h2 id="topics">Topics</h2>
<h3 id="typescript-in-react">TypeScript in React</h3>
<ul>
<li>TypeScript is a super set of JavaScript that adds static types</li>
<li>No need to define classes or interfaces all over the place</li>
</ul>
<h4 id="-01-first-steps-with-typescript">💪 01 First steps with TypeScript</h4>
<p>Open <code>src/exercises/01-first-steps-with-typescript/FirstStepsWithTypeScript.tsx</code></p>
<p><strong>Part 1</strong></p>
<ul>
<li>Create a new component <code>SimpleCounterButton</code>. This component should:
<ul>
<li>Render a button that starts with the number 10</li>
<li>When clicking on the button, the counter should be incremented by 1</li>
<li>Display the current count on the button</li>
</ul>
</li>
<li>Render the button in the FirstStepsWithTypeScript component.</li>
<li>Make sure, that <code>npm run test</code> runs without any errors or warnings.</li>
</ul>
<p><strong>Part 2</strong></p>
<ul>
<li>The SimpleCounterButton should receive a prop "incrementBy". Users of our component should be able to pass in a number that changes the incrmenetation amount. (Instead of always incrementing by 1, the counter should increment by <code>incrementBy</code> on every click)</li>
<li>Users of our component should additionally be able to pass in a <code>style</code> object, that will be forwarded to the <code>style</code>-prop of the <code>button</code> element that is rendered within the SimpleCounterButton. Example: <code><SimpleCounterButton incrementBy={5} style={{ border: 10px solid blue }} /></code> should render a button with a thick blue border.</li>
</ul>
<p><strong>Part 3</strong></p>
<ul>
<li>Create a new component <code>RandomNumbers</code>, that displays a new random number every 5 seconds. For the first 5 seconds, a text <code>Generating a new number</code> should be displayed, which is then replaced by the first random number after 5 seconds.</li>
<li>Render the component next to the SimpleCounterButton.</li>
</ul>
<h3 id="relearn-the-principles-of-react">Relearn the principles of React</h3>
<p>Goal: Understand React instead of just using React</p>
<h4 id="the-rules-of-react">The rules of React</h4>
<ul>
<li>Render functions must be pure</li>
<li>Every created connection to the outside world must be destroyed at some point in time</li>
<li>Don't update data, replace it (immutable updates)</li>
</ul>
<h5 id="-02-fix-the-rule-breaks">💪 02 Fix the rule breaks</h5>
<ul>
<li>Open <code>src/exercises/02-fix-the-rule-breaks/FixTheRuleBreaks.tsx</code></li>
<li>Find all the places where one of React's rules is broken</li>
<li><strong>Before fixing it</strong> try to create a situation in which this rule break would cause problems</li>
<li>Fix the rule break</li>
</ul>
<h4 id="state-in-react">State in React</h4>
<p><strong>Why does a React component re-render?</strong></p>
<ul>
<li>Every render cycle (after the very first) starts with a state change.</li>
<li>useState and useReducer are the two way, we can define a state in React, that can trigger a re-render.</li>
</ul>
<h5 id="-03-impossible-state">💪 03 Impossible state</h5>
<p><strong>Part 1</strong></p>
<ul>
<li>Open <code>src/exercises/03-impossible-state/ImpossibleState.tsx</code></li>
<li>Define two states: <code>username</code> (initialized with an empty string) and <code>isValid</code> (initialized with <code>false</code>)</li>
<li>Render an input field for the current username</li>
<li>Render a button with the text "save". The button should be disabled when isValid is false.</li>
<li>When the username changes, update the isValid state: The username should be between 3 and 12 characters.</li>
<li>Add a second button with the text "reset", that resets the username state.</li>
<li>Add a checkbox with the label "Terms of service accepted" and store the value of the checkbox in a third state (<code>areTermsAccepted</code>)</li>
<li>Make sure that the form is only valid, when the terms are accepted and the username is between 3 and 12 characters long.</li>
</ul>
<p><strong>Do you see how cumbersome it is to make sure the isValid flag is in sync with the state of the form?</strong></p>
<p><strong>Part 2</strong></p>
<ul>
<li>Try to refactor the component so that isValid is no longer a state. The thing to realize it, is that this state is completely dependant on the other two states in the form.</li>
<li>Think about potential disadvantages of this approach.</li>
</ul>
<h5 id="-04-explicit-states">💪 04 Explicit states</h5>
<p><strong>Part 1</strong></p>
<ul>
<li>Open <code>src/exercises/04-explicit-states/ExplicitStates.tsx</code></li>
<li>Use the functions from <code>src/api/satellites.ts</code> to load all satellites.</li>
<li>Display the names of all satellites in a list.</li>
</ul>
<p><strong>Part 2</strong></p>
<ul>
<li>Make sure to handle errors and loading states.</li>
</ul>
<h4 id="useeffect-usememo--usecallback">useEffect, useMemo & useCallback</h4>
<p><strong>What is useEffect for?</strong></p>
<h5 id="-05-unnecessary-useeffects">💪 05 Unnecessary useEffects</h5>
<ul>
<li>Open <code>src/exercises/05-unnecessary-use-effects/UnnecessaryUseEffects.tsx</code></li>
<li>This component currently has some bugs:
<ul>
<li>When selecting a new satellite, the form fields should be populated with the values that are fetched from the server for this satellite.</li>
<li>When creating a new satellite, the list on the left should update</li>
<li>When updating the name of a satellite, the list on the left should update</li>
<li>When switching to a new satellite, the old form state should be discarded</li>
</ul>
</li>
<li>Fix them! :)</li>
</ul>
<h5 id="-06-sync-with-the-outside-world">💪 06 Sync with the outside world</h5>
<p><strong>Part 1</strong></p>
<ul>
<li>Open <code>src/exercises/06-sync-with-the-outside-world/SyncWithTheOutsideWorld.tsx</code></li>
<li>Use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver">ResizeObserver-API</a> to display the current size (width and height) of the textarea below the heading. When you use the resize handle on the textarea, the numbers should update.</li>
<li>Add a button that resets the size of the textarea (the numbers below the heading should update accordingly)</li>
</ul>
<p><strong>Part 2</strong></p>
<ul>
<li>Try to abstract the logic into a reusable custom hook <code>useElementDimensions</code></li>
</ul>
<h5 id="-07-fighting-dependencies">💪 07 Fighting dependencies</h5>
<p><strong>Part 1</strong></p>
<ul>
<li>Open <code>src/exercises/07-fighting-dependencies/FightingDependencies.tsx</code></li>
<li>Implement another counter button: A button that displays a number, and whenever you click the button, the number increments</li>
<li>Below the button, a second counter should be displayed, that automatically increments every 2 seconds by the amount, that is currently displayed on the first button. (When the button shows a 10, the second counter increments by 10 every 2 seconds).</li>
</ul>
<p><strong>Part 2</strong></p>
<ul>
<li>Try to abstract the interval logic into a reusable custom hook <code>useInterval(intervalCallback: () => void, intervalDelay: number)</code></li>
</ul>
<h4 id="imperative-apis-in-react">Imperative APIs in React</h4>
<ul>
<li>React is declarative by default: Data gets passed around and the UI automatically follows</li>
<li>The DOM is mainly imperative: createElement, appendChild, scrollIntoView()</li>
<li>React normally bridges that gap for us for the most part...</li>
<li>For the cases where it does not, there are escape hatches.</li>
</ul>
<h5 id="-08-imperative-apis">💪 08 Imperative APIs</h5>
<p><strong>Part 1</strong></p>
<ul>
<li>Open <code>src/exercises/08-imperative-apis/ImperativeAPIs.tsx</code></li>
<li>Use <code>useRef</code> to get a reference to the input and the list element (you can check <code>src/exercises/06-sync-with-the-outside-world/SyncWithTheOutsideWorld.tsx</code> as a guidance on how to type useRef)</li>
<li>When clicking on the "focus input" button, the textbox should be focussed</li>
<li>When clicking on the "Scoll to element" button, the list should scroll to the n-th element, depending on the current content of the textbox. If a user enters 50 and clicks the scroll button, the 50th li element should be scrolled into the view.</li>
<li>Try to think of more scenarios where imperative APIs may be better than a declarative approach.</li>
</ul>
<p><strong>Optional part 2</strong></p>
<ul>
<li>Read the documentation of <a href="https://beta.reactjs.org/reference/react/useImperativeHandle">useImperativeHandle</a></li>
<li>Extract the list (only the list, none of the buttons or inputs) into a separate component (<code>UserList</code>), where a ref can be attached to imperatively call <code>scrollToElement(index: number)</code></li>
</ul>
<h3 id="component-patterns">Component Patterns</h3>
<p>When designing components, you have to pick 2 out of the 3 following facets:
<img src="src/assets/component-spectrum.png" alt="The spectrum of component APIs"></p>
<p><strong>Note:</strong> Nothing is inherently wrong with any spot of this triangle, it depends on your goals and circumstances.</p>
<p>Moving around within this triangle is done by managing decisions:</p>
<p><img src="src/assets/component-decisions.png" alt="Decisions within a component"></p>
<h4 id="the-children-prop-or-jsx-as-props">The children prop or JSX as props</h4>
<p><strong>Deciding what contents should be rendered within a component</strong></p>
<h5 id="-09-layout-components">💪 09 Layout-components</h5>
<p><strong>Part 1</strong></p>
<ul>
<li>Open <code>src/exercises/09-layout-components/LayoutComponents.tsx</code>.</li>
<li>Build a new component <code>PageContent</code>, that renders a horizontally centered div with a max width of 700px.</li>
<li>The PageContent component should not decide what is rendered within that component, but should simply render its children. The types for the children prop can be defined like this: <code>function Test(props: { children: ReactNode }) {...}</code></li>
<li>Render the heading "Layout Components" within a PageComponent tag.</li>
</ul>
<p><strong>Part 2</strong></p>
<ul>
<li>Build a new component <code>ContentWithSidebar</code>. This accepts to props: <code>sidebarContent</code> and <code>mainContent</code>, both <code>ReactNode</code>s.</li>
<li>The ContentWithSidebar component should render the sidebar content on the left, and next to it, the main content.</li>
<li>Use the ContentWithSidebar component in <code>LayoutComponents</code></li>
</ul>
<p><strong>Part 3</strong></p>
<ul>
<li>Add an <code><input type="range" /></code> to the ContentWithSidebar component. Whenever users change this range slider, the width of the sidebar should adjust on the fly. Use the min and max prop on the input to define that the slide picks values between 100 and 400.</li>
<li>Try to figure out, how often the mainContent is being rerendered, when users adjust the slide value.</li>
</ul>
<p><strong>Optional part 4</strong></p>
<ul>
<li>Render a border between the sidebar and the main content</li>
<li>Allow users to change the position of the border (and thus the width of the sidebar) via drag and drop.</li>
</ul>
<h4 id="render-props">Render Props</h4>
<ul>
<li>We were now able to move out the decision what to render.</li>
<li>However, sometimes, the rendered content needs to access data from the wrapper component.</li>
<li>Additionally, we now realized, that we can pass around JSX like any other piece of data.</li>
</ul>
<p><strong>Render props: Returning JSX from functions</strong></p>
<h5 id="-10-render-props">💪 10 Render props</h5>
<ul>
<li>Open <code>src/exercises/10-render-props/RenderProps.tsx</code></li>
<li>Copy over the solution from the previous task.</li>
<li>Adapt the solution so that you can display the width of the sidebar within the sidebar content, by changing the prop definition from <code>sidebarContent: ReactNode</code> to <code>renderSidebar: (width: number) => ReactNode</code></li>
<li>Now, try to find out, how many times the sidebar content rerenders when users adjust the width of the sidebar with the slider.</li>
</ul>
<p><strong>Optional Part 2 (involves TypeScript generics)</strong></p>
<ul>
<li>Define a new component <code>List</code></li>
<li>The list expects a prop <code>items</code> which is a list of any data type (use a generic type parameter to capture this data type: <code>function List<TItem>(props: { items: TItem[] }) {...}</code>)</li>
<li>Within the list, render a <code>ul</code> and within that, loop over the passed items and use a render prop (<code>eg. renderItem</code>) to determine the content that needs to be rendered for the given item.</li>
</ul>
<h4 id="compound-components">Compound Components</h4>
<ul>
<li>We were now able to pass data from our library component ContentWithSidebar to our application-level component.</li>
<li>This <strong>can</strong> have a couple of downsides:
<ul>
<li>Performance: Every state change in the library component triggers a re-render of the application component.</li>
<li>Effort: Passing data down multiple levels this way can become tedious.</li>
<li>Error-Prone: When other library components need access to this data, application code needs to pass around the data which can be forgotten.</li>
</ul>
</li>
</ul>
<p>To mitigate those, build component components: One conceptual component, that is split into multiple React-components but are still connected internally via React-Context.</p>
<pre><code class="language-tsx"><span class="hljs-comment">// Library</span>
<span class="hljs-keyword">const</span> <span class="hljs-title class_">CounterContext</span> = createContext<{ <span class="hljs-attr">counter</span>: number } | <span class="hljs-literal">null</span>>(<span class="hljs-literal">null</span>);
<span class="hljs-keyword">function</span> <span class="hljs-title function_">CounterButton</span>(<span class="hljs-params">props: { children: ReactNode }</span>) {
<span class="hljs-keyword">const</span> [counter, setCounter] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">0</span>);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =></span> setCounter(counter + 1)}>
<span class="hljs-tag"><<span class="hljs-name">CounterContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">counter</span> }}></span>
{props.children}
<span class="hljs-tag"></<span class="hljs-name">CounterContext.Provider</span>></span>
<span class="hljs-tag"></<span class="hljs-name">button</span>></span></span>
);
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">useCounter</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> counterValue = <span class="hljs-title function_">useContext</span>(<span class="hljs-title class_">CounterContext</span>);
<span class="hljs-keyword">if</span> (!counterValue) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">"No CounterContext.Provider found."</span>);
}
<span class="hljs-keyword">return</span> counterValue.<span class="hljs-property">counter</span>;
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">CounterDisplay</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> counter = <span class="hljs-title function_">useCounter</span>();
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><></span>{counter}<span class="hljs-tag"></></span></span>;
}
<span class="hljs-comment">// App code:</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">CounterButton</span>></span>
The current count is at <span class="hljs-tag"><<span class="hljs-name">CounterDisplay</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">CounterButton</span>></span></span>
);
}
</code></pre>
<h5 id="-11-compound-components">💪 11 Compound components</h5>
<p><strong>Part1</strong></p>
<ul>
<li>Start with your solution from <code>src/exercises/09-layout-components/LayoutComponents.tsx</code></li>
<li>Define a new component <code>SidebarWidth</code> that displays the sidebar width of the nearest ContentWithSidebar component (define and use a ReactContext for this).</li>
<li>Use your SidebarWidth component, to render the width of the sidebar.</li>
<li>Check how often the rest of your sidebar content re-renders with this approach.</li>
</ul>
<p><strong>Part2</strong></p>
<ul>
<li>Try to find examples in libraries that you have used in your projects, that apply this pattern.</li>
</ul>
<h3 id="additional-topics">Additional topics</h3>
<h4 id="security-in-react">Security in React</h4>
<h4 id="-12-testing-in-react-applications">💪 12 Testing in React applications</h4>
<p><strong>We are using vitest</strong> in this project. It has mostly the same APIs as Jest but is much faster and reuses <code>vite.config.ts</code>.</p>
<ul>
<li>Open <code>src/exercises/12-testing-in-react-applications/TestingInReactApplications.tsx</code> and the test file directly next to it.</li>
<li>Run <code>npx vitest</code> in a terminal to start the test runner vitest in watch mode.</li>
</ul>
<p><strong>Part 1</strong>: Unit tests</p>
<ul>
<li>Extract the main logic out of <code>FizzBuzzButton</code> and export it as a function</li>
<li>Write test cases for that new function that checks the following properties:
<ul>
<li>It should return the fizzValue, a space and the buzzValue for numbers that are divisible by 3 and 5</li>
<li>It should return the fizzValue for numbers divisible by 3</li>
<li>It should return the buzzValue for numbers divisible by 5</li>
<li>It should return the number for all other cases.</li>
</ul>
</li>
</ul>
<p><strong>Part 2</strong>: Component tests</p>
<ul>
<li>Use the documentation for <a href="https://testing-library.com/docs/react-testing-library/example-intro">React testing library</a> to render the FizzBuzzButton component and check that it renders without errors and that it follows the fizz buzz rules.</li>
</ul>
<p><strong>Part 3</strong>: Components with side effects</p>
<ul>
<li>Use the same React testing library guide, together with <a href="https://mswjs.io/docs/">msw</a>, to test the <code>SatelliteList</code> component and check if it displays a loading indicator initially, if it displays an error message when the request fails, and if data is displayed if the request succeeds.</li>
</ul>
<link rel="stylesheet" href="./README.css">
<script src="./README.js"></script>
</body>
</html>