Skip to content

Commit e2398a6

Browse files
committed
Starting point
1 parent e3a4e1d commit e2398a6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1026
-213
lines changed

LICENSE.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Josh's Course Materials License
2+
3+
Version 1, November 2020
4+
Copyright (c) Josh Comeau, 2020-present
5+
6+
The files in this repository are meant to be used as part of a paid course, and are not intended for public distribution. They're open-source because it's the simplest form of distribution, and provides the best experience for students enrolled in the course.
7+
8+
All are welcome to create personal copies of this repository, and modify its contents for educational use. Please experiment with the code, and see what you can build!
9+
10+
It is forbidden to use these contents in any sort of commercial endeavour, including but not limited to:
11+
12+
• Reselling its contents as part of a different course
13+
• Incorporating the code into a pre-existing business or project
14+
• Selling your solution to students enrolled in the course
15+
16+
Exemptions can be made, on a case-by-case basis. Contact Josh Comeau ([email protected]) for more information.

README.md

+199-38
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,231 @@
1-
# Getting Started with Create React App
1+
# Toast Component Project
22

3-
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
3+
## Joy of React, Project II
44

5-
## Available Scripts
5+
In this project, we'll dive deep into the implementation of a single common UI component: A `<Toast>` message component.
66

7-
In the project directory, you can run:
7+
![Screen recording showing 3 toast messages popping up from user input](./docs/toast-demo.gif)
88

9-
### `npm start`
9+
## Getting Started
1010

11-
Runs the app in the development mode.\
12-
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
11+
This project is created with create-react-app. It's intended to be run locally, on your computer, using Node.js and NPM.
1312

14-
The page will reload when you make changes.\
15-
You may also see any lint errors in the console.
13+
During the first project, Wordle, we saw how to run a local development server. If you're not quite sure how to get started, I recommend reviewing the [“Local Development” instructions](https://courses.joshwcomeau.com/joy-of-react/project-wordle/03-dev-server) lesson.
1614

17-
### `npm test`
15+
To jog your memory, here are the terminal commands you'll need to run:
1816

19-
Launches the test runner in the interactive watch mode.\
20-
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
17+
```bash
18+
# Install dependencies:
19+
npm install
2120

22-
### `npm run build`
21+
# Run a development server:
22+
npm run start
23+
```
2324

24-
Builds the app for production to the `build` folder.\
25-
It correctly bundles React in production mode and optimizes the build for the best performance.
25+
To create new components, you can use this helper script. It saves you a bit of time, creating all the files and adding the standard code:
2626

27-
The build is minified and the filenames include the hashes.\
28-
Your app is ready to be deployed!
27+
```bash
28+
# Create a new component:
29+
npm run new-component [TheNewComponentName]
30+
```
2931

30-
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
32+
---
3133

32-
### `npm run eject`
34+
## Exercise 1: Wiring up form controls
3335

34-
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
36+
In order to test our `Toast` component, we'll start by building a little playground. This will allow us to test our component throughout development.
3537

36-
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
38+
![Image showing a textarea and set of radio buttons, along with a “Pop Toast!” radio button](./docs/playground.png)
3739

38-
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
40+
In `ToastPlayground.js`, you'll find most of the markup you'll need, but there are two problems:
3941

40-
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
42+
1. All of the inputs are _uncontrolled_, meaning we can't easily access their values in React. We should use React state to drive all form controls.
43+
2. We're only given a single radio button. We need one for each valid variant.
4144

42-
## Learn More
45+
**This first exercise is meant to be a review of the concepts learned in Module 1 and Module 2.** So, it might be worth brushing up on some of those earlier lessons. In particular, the [Input Cheatsheet bonus lesson](https://courses.joshwcomeau.com/joy-of-react/02-state/09-bonus-cheatsheet) has some handy info about binding different types of form inputs!
4346

44-
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
47+
**Acceptance Criteria:**
4548

46-
To learn React, check out the [React documentation](https://reactjs.org/).
49+
- The “Message” textarea should be driven by React state
50+
- Using the data in the `VALID_VARIANTS` array, render 4 radio buttons within the “Variant” row. They should all be part of the same group (so that only one can be selected at a time). They should also be driven by React state.
51+
- There should be no key warnings in the console.
4752

48-
### Code Splitting
53+
---
4954

50-
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
55+
## Exercise 2: Customizing the Toast component
5156

52-
### Analyzing the Bundle Size
57+
Inside `src/components`, you'll find a `Toast` component. This component includes the basic DOM structure you'll need, but **it's entirely static right now.** It doesn't accept any props!
5358

54-
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
59+
Your mission in this exercise is to render the `Toast` component within `ToastPlayground` and allow the playground to customize the Toast using the state we set up in the previous exercise. We should also figure out a "dismissal" mechanism, so that the close button functions.
5560

56-
### Making a Progressive Web App
61+
Here's what it should look like, when you've solved this exercise:
5762

58-
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
63+
![Screen recording showing how a demo Toast component can be edited using the playground](./docs/toast-exercise-2-demo.gif)
5964

60-
### Advanced Configuration
65+
For now, you can import the `Toast` component in `ToastPlayground` and render it between the header and the controls:
6166

62-
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
67+
```jsx
68+
<header>
69+
<img alt="Cute toast mascot" src="/toast.png" />
70+
<h1>Toast Playground</h1>
71+
</header>
6372

64-
### Deployment
73+
{/* Place a <Toast /> here! */}
6574

66-
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
75+
<div className={styles.controlsWrapper}>
76+
<div className={styles.row}>
77+
```
6778

68-
### `npm run build` fails to minify
79+
**It's up to you to come up with the best possible “Prop API” for this component!**
6980

70-
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
81+
If you get stuck, you may wish to review the following lessons from the course:
82+
83+
- [Styling in React, exercises](https://courses.joshwcomeau.com/joy-of-react/01-fundamentals/09.02-styling-exercises)
84+
- [Slots, exercises](https://courses.joshwcomeau.com/joy-of-react/04-component-design/07.01-slots-exercises) (Especially the stretch goal from the first exercise!)
85+
86+
**Acceptance Criteria:**
87+
88+
- The toast component should show the message entered in the textarea, essentially acting as a “live preview”.
89+
- The toast's styling should be affected by the “variant” selected:
90+
- The colors can be set by specifying the appropriate class on the top-level `<div>`. By default, it's set to `styles.notice`, but you'll want to dynamically select the class based on the variant (eg. for a success toast, you'll want to apply `styles.success`).
91+
- The icon can be selected from the `ICONS_BY_VARIANT` object. Feel free to re-organize things however you wish!
92+
- The toast should be hidden by default, but can be shown by clicking the "Pop Toast!” button.
93+
- The toast can be hidden by clicking the “×” button within the toast.
94+
95+
---
96+
97+
## Exercise 3: Toast Shelf
98+
99+
One of the core defining characteristics of toast notifications is that they stack!
100+
101+
![Several toasts popping up, one after another](./docs/poppin-toasts.gif)
102+
103+
Your mission in this exercise is to restructure things so that our `ToastPlayground` allows us to create _multiple_ toasts.
104+
105+
To help in your quest, you'll find a `ToastShelf` component in this project. It will automatically apply the styles and animations.
106+
107+
You'll need to replace the `Toast` live demo with this new `ToastShelf` component, inside `ToastPlayground`:
108+
109+
```diff
110+
<header>
111+
<img alt="Cute toast mascot" src="/toast.png" />
112+
<h1>Toast Playground</h1>
113+
</header>
114+
115+
- <Toast />
116+
+ <ToastShelf />
117+
118+
<div className={styles.controlsWrapper}>
119+
<div className={styles.row}>
120+
```
121+
122+
By the end of this exercise, it should look like this:
123+
124+
![Screen recording showing toast messages popping up when “Pop Toast!” is clicked](./docs/toast-exercise-3-demo.gif)
125+
126+
**This is a very tricky exercise.** If you're not sure where to start / how to make this work, I share some [hints on the course platform](/joy-of-react/project-toast/04-solution).
127+
128+
Some lessons that might help, from the course:
129+
130+
- [The onClick Parable](https://courses.joshwcomeau.com/joy-of-react/02-state/04.02-on-click-parable)
131+
- [Dynamic key generation](https://courses.joshwcomeau.com/joy-of-react/02-state/07-key-generation)
132+
133+
**Acceptance Criteria:**
134+
135+
- Instead of live-editing a single Toast instance, the playground should be used to push new toast messages onto a stack, rendered inside `ToastShelf` and shown in the corner of the page.
136+
- When “Pop Toast!” is clicked, the message/variant form controls should be reset to their default state (`message` should be an empty string, `variant` should be "notice").
137+
- Clicking the “×” button inside the toast should remove that specific toast (but leave the rest untouched).
138+
- A proper `<form>` tag should be used in the `ToastPlayground`. The toast should be created when submitting the form.
139+
- See for more information.
140+
- **There should be no key warnings in the console!** Keys should be unique, and you should not use the index.
141+
142+
---
143+
144+
## Exercise 4: Context
145+
146+
As it stands, all of our state has been managed by `ToastPlayground`. This works in the context of our little demo app, but it wouldn't scale well in a real-world application!
147+
148+
In this exercise, we'll refactor our application to use a [Provider component](https://courses.joshwcomeau.com/joy-of-react/04-component-design/08.04-provider-component). It will own all of the state related to the toasts state, and make it available to any child component who requires it.
149+
150+
**Acceptance Criteria:**
151+
152+
- Create a new component, `ToastProvider`, that will serve as the “keeper” for toast-related state.
153+
- To generate a new component, you can use the “new-component” script! Try tunning `npm run new-component ToastProvider` in the terminal.
154+
- Components that require the state should pull it from context with the `useContext` hook, rather than passing through props.
155+
- As we saw in the [“Provider Components” lesson](https://courses.joshwcomeau.com/joy-of-react/04-component-design/08.04-provider-component), we can also share _functions_ that allow consumers to alter the state. Consider making functions available that will create a new toast, or dismiss a specific toast.
156+
- This is a “refactor” exercise. The user experience shouldn't change at all.
157+
158+
---
159+
160+
## Exercise 5: Keyboard and screen reader support
161+
162+
Our component so far works pretty well for sighted mouse users, but the experience isn't as great for everyone.
163+
164+
For keyboard users, let's add a global event handler that listens for the “escape” key, and dismisses _all_ toasts when it's pressed.
165+
166+
For screen-reader users, we need to change some things in our markup.
167+
168+
In `ToastShelf.js`, add the following 3 attributes to the wrapping `<ol>`, so that the markup looks like this:
169+
170+
```diff
171+
<ol
172+
class="wrapper"
173+
+ role="region"
174+
+ aria-live="assertive"
175+
+ aria-label="Notification"
176+
>
177+
```
178+
179+
In `Toast.js`, make the following changes:
180+
181+
- Within the paragraph that holds the message content, prefix it with the variant, so that the final output looks something like this:
182+
183+
```diff
184+
<p class="content">
185+
+ <span class="visually-hidden">error -</span>
186+
Your account could not be found
187+
</p>
188+
```
189+
190+
- Update the close button so that it uses an `aria-label` instead of the `<VisuallyHidden>` helper:
191+
192+
```diff
193+
<button
194+
class="closeButton"
195+
+ aria-label="Dismiss message"
196+
+ aria-live="off"
197+
>
198+
<svg>X</svg>
199+
- <span class="visually-hidden">Dismiss message</span>
200+
</button>
201+
```
202+
203+
I realize that these changes seem totally arbitrary / we haven't learned about this stuff! In the solution video, I'll explain exactly why all these changes are necessary.
204+
205+
---
206+
207+
## Exercise 6: Custom hooks
208+
209+
Whew! We've done quite a bit with this lil’ `Toast` component!
210+
211+
In the last exercise, we added an “escape” keyboard shortcut, to dismiss all toasts in a single keystroke. This is a very common pattern, and it requires a surprising amount of boilerplate in React.
212+
213+
Let's build a **custom reusable hook** that makes it easy to reuse this boilerplate to solve future problems.
214+
215+
There are lots of different ways to tackle this, and there's no right or wrong answer, but here's one idea to get you started: what if we create a new custom hook called `useEscapeKey`?
216+
217+
```js
218+
useEscapeKey(() => {
219+
// Code to dismiss all toasts
220+
});
221+
```
222+
223+
**This is an open-ended exercise.** Feel free to experiment with different APIs and see what works best for you!
224+
225+
**Acceptance Criteria:**
226+
227+
- Create a new `/src/hooks` directory, and add a new `.js` file for your custom hook. You can name it whatever you'd like.
228+
- Copy over the boilerplate for event listening (eg. the `useEffect` hook, the `addEventListener` call, the cleanup function…) into this new custom hook.
229+
- Replace the "escape" key handling with this new custom hook.
230+
- **Make sure there are no ESLint warnings.**
231+
- In VSCode, ESLint warnings are shown as squiggly yellow underlines. You can view the warning by hovering over the underlined characters, or by opening the “Problems” tab (`` + `Shift` + `M`, or Ctrl + `Shift` + `M`).

0 commit comments

Comments
 (0)