` with
a fragment and inspect the DOM to notice that the elements are both rendered as
direct children of `root`.
+=======
+This extra credit primes us for the next exercise which is a different example,
+so you'll find starter code for this extra credit in `exercise/04.extra-4.html`.
+>>>>>>> f70d1e3 (next)
-## 🦉 Feedback
+## 🦉 Elaboration and Feedback
Fill out
[the feedback form](https://ws.kcd.im/?ws=React%20Fundamentals%20%E2%9A%9B&e=04%3A%20Creating%20custom%20components&em=).
diff --git a/src/exercise/05.md b/src/exercise/05.md
index c04acecc4..949dc7397 100644
--- a/src/exercise/05.md
+++ b/src/exercise/05.md
@@ -1,4 +1,4 @@
-# Styling
+# TypeScript with React
## 📝 Your Notes
@@ -6,125 +6,353 @@ Elaborate on your learnings here in `src/exercise/05.md`
## Background
-There are two primary ways to style react components
+TypeScript is an enormously valuable team productivity, code quality, and
+confidence tool that I strongly recommend you use to build any React application
+you plan making maintainable.
-1. Inline styles with the `style` prop
-2. Regular CSS with the `className` prop
+> ‼️ NOTE: If you're not already familiar with TypeScript, then you may want to
+> skip the TypeScript parts of the exercises. The workshop assumes experience
+> with TypeScript and will therefore be very difficult to complete if you don't
+> already have experience with TypeScript. Remember, you can remove the
+> TypeScript from the workshop by running `./scripts/remove-ts` in your command
+> line.
-**About the `style` prop:**
+Remember, in exercise 4 we learned what a React component is:
-- In HTML you'd pass a string of CSS:
+> Components are functions which accept an object called "props" and return
+> something that is renderable
-```html
-
+Because of this, they don't require any special considerations when applying
+TypeScript type annotations to them. You treat React component functions the
+same way you treat regular functions. Because of this, the major battle for
+folks using TypeScript with React is:
+
+1. Improving their TypeScript skills
+2. Learning the React-specific types available
+
+📜 I advise having the
+[React+TypeScript Cheatsheets](https://github.com/typescript-cheatsheets/react)
+repo open as a reference while you're getting used to using TypeScript and
+React.
+
+With that said, here's a quick intro to adding type annotations to functions:
+
+```tsx
+// here's a regular JS function that accepts a user
+// which might have a name property
+function getName(user) {
+ return user.name ?? 'Unknown'
+}
+
+// here's how you'd type that user object to say it has an optional name property:
+type User = {name?: string}
+
+// and here's how you'd tell TypeScript that the user parameter is a User object
+function getName(user: User) {
+ return user.name ?? 'Unknown'
+}
+
+// and if you'd like to, you can specify the return type explicitely as well
+// (though it is inferred):
+function getName(user: User): string {
+ return user.name ?? 'Unknown'
+}
```
-- In React, you'll pass an object of CSS:
+📜 Learn more about the syntax for functions in TypeScript here:
+[TypeScript Function Syntaxes](https://kentcdodds.com/blog/typescript-function-syntaxes)
+
+Let's look at a quick example of adding TypeScript to a simple (and familiar)
+React component:
+
+```tsx
+function Message(props) {
+ return
{props.children}
+}
+
+// We could say:
+type MessageProps = {children: string}
+function Message(props: MessageProps) {
+ return
{props.children}
+}
+// that would allow:
Hello World
+// but not:
Hello World
+
+// The React types have a ReactNode, which is the recommended choice for the children prop:
+type MessageProps = {children: React.ReactNode}
+function Message(props: MessageProps) {
+ return
{props.children}
+}
-```jsx
-
+// keep in mind that you don't *have* to give your props a name.
+// You can inline them as well. This works just the same as above:
+function Message(props: {children: React.ReactNode}) {
+ return
{props.children}
+}
+
+// and you can destructure as well:
+function Message({children}: {children: React.ReactNode}) {
+ return
{children}
+}
+
+// mix-and-match (this is what I do most of the time):
+type MessageProps = {children: React.ReactNode}
+function Message({children}: MessageProps) {
+ return
{children}
+}
```
-Note that in react the `{{` and `}}` is actually a combination of a JSX
-expression and an object expression. The same example above could be written
-like so:
+📜 Learn more about typing React components here:
+[How to write a React Component in TypeScript](https://kentcdodds.com/blog/how-to-write-a-react-component-in-typescript)
+
+🦉 It's great to have your code type checked, but not at the expense of
+progressing and learning. If you get totally stuck on something, then I suggest
+you tell TypeScript to quiet down and come back to it later when you're more
+experienced or want to focus on it. You can do this like so:
-```jsx
-const myStyles = {marginTop: 20, backgroundColor: 'blue'}
-
+```typescript
+// @ts-expect-error TypeScript is complaining about this next line.
+// Something about magic not existing.
+// I don't know how to fix this right now... Come back later.
+make.magic()
```
-Note also that the property names are `camelCased` rather than `kebab-cased`.
-This matches the `style` property of DOM nodes (which is a
-[`CSSStyleDeclaration`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration)
-object).
+💰 Tip: If you're an experienced TypeScript developer and want to enable strict
+mode, open `config/tsconfig.exercise.json` and set `strict` to `true` in there.
-**About the `className` prop:**
+💰 Tip: If you're not interested in using TypeScript and would rather work
+through all the exercises with JavaScript, then run this in your terminal:
+
+```
+./scripts/remove-ts
+```
-As we discussed earlier, in HTML, you apply a class name to an element with the
-`class` attribute. In JSX, you use the `className` prop.
+This will convert all the files to JavaScript. You may need to restart your
+development server for the change to take effect.
## Exercise
Production deploys:
-- [Exercise](http://react-fundamentals.netlify.app/isolated/exercise/05.js)
-- [Final](http://react-fundamentals.netlify.app/isolated/final/05.js)
+- [Exercise](http://react-fundamentals-next.netlify.app/isolated/exercise/05.tsx)
+- [Final](http://react-fundamentals-next.netlify.app/isolated/final/05.tsx)
+
+In this exercise, we're going to take the `Calculator` component we have in the
+extra credit of the previous exercise and add type annotations to it.
+
+We're moving from the `*.html` files to `*.tsx` files. This is more real-world
+and also your editor likely supports TypeScript better within TypeScript files
+rather than HTML files. From here on out, every exercise will export an `App`
+component. The workshop app will take care of rendering that for us. The
+workshop app is a little magical, so you probably won't find where it happens,
+but rest assured, it's just calling `ReactDOM.render` under the hood, just like
+we were doing in the previous exercises.
+
+Moving to `*.tsx` files also means that you can use the tests to help validate
+your solution is correct. It's Alfred the Alert's time to shine 🚨!
+
+As a reminder, to get the tests running, in a separate terminal window, run
+`npm test`, then open the test file for the exercise you're working on and
+update the imports:
+
+```diff
+ import {render} from '@testing-library/react'
+ import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils'
+- import {App} from '../final/05'
+- // import {App} from '../exercise/05'
++ // import {App} from '../final/05'
++ import {App} from '../exercise/05'
+```
-In this exercise we'll use both methods for styling react components.
+In addition to typing the function itself, we'll also be able to play around
+with some approaches to typing the `operations` object to make things easier for
+us as well as people using our component.
-We have the following css on the page:
+I'd also like you to meet Lily the Life Jacket! 🦺 She's going to be hanging
+around the rest of the crew to indicate wherever there's something that's
+TypeScript-specific you need to do and to give you TypeScript-specific tips.
+You'll be working with Lily the Life Jacket a lot in this exercise.
-```css
-.box {
- border: 1px solid #333;
- display: flex;
- flex-direction: column;
- justify-content: center;
- text-align: center;
-}
-.box--large {
- width: 270px;
- height: 270px;
-}
-.box--medium {
- width: 180px;
- height: 180px;
-}
-.box--small {
- width: 90px;
- height: 90px;
-}
+Now, open `src/exercise/05.tsx` and follow the emoji there. You'll notice that
+instead of calling `ReactDOM.render`, we're exporting an `App` component. This
+is how the rest of the workshops will work. Rest assured, `ReactDOM.render` _is_
+being called for you under the hood, just like we were doing earlier.
+
+## Extra Credit
+
+### 1. 💯 improve autocomplete for the operator string
+
+[Production deploy](http://react-fundamentals-next.netlify.app/isolated/final/05.extra-1.tsx)
+
+Our `CalculatorProps['operator']` type being set simply to `string` is not
+"narrow" enough to help users of our `Calculator` component. It allows _any_
+`string` value to be provided, even one which our Calculator doesn't support.
+For example, the exponentiation operator `**` could be passed and TypeScript
+won't complain, but this would cause a runtime error because we don't have a
+function to handle that operator:
+
+```tsx
+element =
// 💥
```
-Your job is to apply the right className and style props to the divs so the
-styles applied match the text content.
+On top of that, the API for our `Calculator` isn't very discoverable. How would
+people know which `operations` are possible? Docs? Trial and error?
-## Extra Credit
+Rather than a `string`, your TypeScript type definition can be set to a specific
+string. For example:
-### 1. 💯 Create a custom component
+```tsx
+type KodyString = 'Kody'
+let kody: KodyString // this variable can only ever be set to the string 'Kody'
+```
+
+Combine that functionality with
+[union syntax of `|`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)
+and you'll be able to specify exactly which operators are allowed. For example:
-[Production deploy](http://react-fundamentals.netlify.app/isolated/final/05.extra-1.js)
+```tsx
+type KodyOrHannahString = 'Kody' | 'Hannah'
+let assistant: KodyOrHannahString // this variable can only ever be set to the string 'Kody' or 'Hannah'
+
+// 💰 tip: we could do the same thing without creating a type by inlining instead:
+// let assistant: 'Kody' | 'Hannah'
+```
-Try to make a custom `
` component that renders a div, accepts all the
-props and merges the given `style` and `className` props with the shared values.
+How about we narrow our `operator` type from a `string` to some specific strings
+using a union.
-I should be able to use it like so:
+### 2. 💯 derive the operator type from the operations object
-```jsx
-
- small lightblue box
-
+[Production deploy](http://react-fundamentals-next.netlify.app/isolated/final/05.extra-2.tsx)
+
+You may have noticed that we're duplicating our operators of `+`, `-`, `*`, and
+`/`. Any time we want to add a new operator, we have to add it in two places and
+if we miss one then we could either have a runtime error, or users won't be able
+to use our new operator at all.
+
+It would be better if we could have the compiler let us know we missed one
+(foreshadowing... look forward to that in an upcoming extra credit) or just
+derive the possible operators.
+
+To do this, you need to know about two TypeScript keywords: `typeof` and
+`keyof`. Technically `typeof` is a JavaScript feature, but TypeScript builds on
+top of this and will get you the TypeScript type for the given variable. So if
+you say:
+
+```tsx
+const user = {name: 'kody', isCute: true}
+type User = typeof user
+// type User = { name: string; isCute: boolean; }
```
-The `box` className and `fontStyle: 'italic'` style should be applied in
-addition to the values that come from props.
+And then you can use `keyof` to get a union-ed type of strings of all the keys
+in a given type:
+
+```tsx
+type UserKeys = keyof User
+// type UserKeys = "name" | "isCute"
+```
+
+📜 Learn more about TypeScript's
+[`typeof` operator](https://www.typescriptlang.org/docs/handbook/2/typeof-types.html)
+and
+[`keyof` operator](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html).
+
+With that, try and derive the type of the `CalculatorProps['operator']` so you
+don't have to repeat yourself.
-### 2. 💯 accept a size prop to encapsulate styling
+### 3. 💯 default prop values
-[Production deploy](http://react-fundamentals.netlify.app/isolated/final/05.extra-2.js)
+[Production deploy](http://react-fundamentals-next.netlify.app/isolated/final/05.extra-3.tsx)
-It's great that we're composing the `className`s and `style`s properly, but
-wouldn't it be better if the users of our components didn't have to worry about
-which class name to apply for a given effect? Or that a class name is involved
-at all? I think it would be better if users of our component had a `size` prop
-and our component took care of making the box that size.
+Sometimes you want to allow the user of your component to skip providing a prop
+and use a default value instead. To do this, we can use
+[destructuring default values syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Default_values_2),
+but when users of our component try to skip a prop, TypeScript will complain
+because our type says all the elements of the `CalculatorProps` type are
+required.
-In this extra credit, try to make this API work:
+So when you make a prop optional, make sure you provide any relevant default
+value as well as mark it as optional using the
+[optional properties syntax](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties):
-```jsx
-
- small lightblue box
-
+```tsx
+type User = {name: string; isCute?: boolean}
+// name is required, isCute is optional, so these both compile:
+const kody = {name: 'Kody', isCute: true}
+const peter = {name: 'Peter'}
```
-## Attribution
+For this extra credit, make all props optional. Default `left` and `right` to
+`0` and `operator` to `'+'`. Then you can update the App to test it out:
+
+```tsx
+function App() {
+ return (
+
+
Calculator
+
+
+
+
+
+ )
+}
+```
+
+### 4. 💯 reduce duplication for operation functions
+
+[Production deploy](http://react-fundamentals-next.netlify.app/isolated/final/05.extra-4.tsx)
+
+🦉 These last two extra credits have little to do with React and everything to
+do with TypeScript. If you'd rather skip these two, I won't be offended 🥲
+
+One last thing that bugs me is the repetition in the `operations` type. The type
+for every one of those functions is the same. They all accept two numbers and
+return a number.
+
+One thing we could do is extract that function into a type and then tell
+TypeScript that the `operations` object is a `Record` where the key is one of
+the valid operators and the value is an `OperationFn`.
+
+I'm going to let you try this one on your own.
+
+💰 But I'll give you some hints:
+
+- You'll need 📜
+ [TypeScript's `Record` Utility Type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype)
+- You'll have to manually create a union of all allowed `operations` again for
+ the Record's `key`
+- You'll need to define 📜
+ [a function type](https://kentcdodds.com/blog/typescript-function-syntaxes)
+ for the Record's value
+
+🦉 At the end of this one, you may prefer the previous version and that's fine.
+This is just two ways to do it and they both come with trade-offs. Personally, I
+prefer this way to avoid typing all the functions individually.
+
+🦉 Also, you may wonder why we went back to repeating ourselves. Unfortunately
+there's no way around it if you want to define the object as a Record with a
+specific key. However! It's not as bad as before because if we make a mistake
+and forget to update both places, the compiler will complain at us rather than
+having a runtime error, so it's less of a problem. And there's actually a
+workaround for this, which is what the next extra credit is all about!
+
+### 5. 💯 use a "Constrained Identity Function (CIF)"
+
+[Production deploy](http://react-fundamentals-next.netlify.app/isolated/final/05.extra-5.tsx)
+
+Ok, so repeating ourselves there is not awesome. The problem is that we want to
+enforce the value of our `operations` object, but to do that we either have to
+widen the type of our `key` or list it explicitly as we're doing.
+
+What we need is some way to enforce the values of our object, without having to
+annotate our object. That's what a CIF is. I've written a blog post to describe
+this, so I'll let you go through that, and then try to make that work yourself:
-[Matt Zabriskie](https://twitter.com/mzabriskie) developed this example
-originally for
-[a workshop we gave together.](https://github.com/mzabriskie/react-workshop)
+**[How to write a Constrained Identity Function (CIF) in TypeScript](https://kentcdodds.com/blog/how-to-write-a-constrained-identity-function-in-typescript)**
## 🦉 Feedback
Fill out
-[the feedback form](https://ws.kcd.im/?ws=React%20Fundamentals%20%E2%9A%9B&e=05%3A%20Styling&em=).
+[the feedback form](https://ws.kcd.im/?ws=React%20Fundamentals%20%E2%9A%9B&e=05%3A%20TypeScript%20with%20React&em=).
diff --git a/src/exercise/05.tsx b/src/exercise/05.tsx
new file mode 100644
index 000000000..c25012c51
--- /dev/null
+++ b/src/exercise/05.tsx
@@ -0,0 +1,38 @@
+// TypeScript with React
+// http://localhost:3000/isolated/exercise/05.tsx
+
+// 🦺 add type definitions for each function
+const operations = {
+ '+': (left, right) => left + right,
+ '-': (left, right) => left - right,
+ '*': (left, right) => left * right,
+ '/': (left, right) => left / right,
+}
+
+// 🦺 create a type called CalculatorProps
+
+// 🦺 set the type for this props argument to CalculatorProps
+function Calculator({left, operator, right}) {
+ const result = operations[operator](left, right)
+ return (
+
+
+ {left} {operator} {right} =
+
+
+ )
+}
+
+function App() {
+ return (
+
+
Calculator
+
+
+
+
+
+ )
+}
+
+export {App}
diff --git a/src/exercise/06.js b/src/exercise/06.js
deleted file mode 100644
index e454609af..000000000
--- a/src/exercise/06.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Basic Forms
-// http://localhost:3000/isolated/exercise/06.js
-
-import * as React from 'react'
-
-function UsernameForm({onSubmitUsername}) {
- // 🐨 add a submit event handler here (`handleSubmit`).
- // 💰 Make sure to accept the `event` as an argument and call
- // `event.preventDefault()` to prevent the default behavior of form submit
- // events (which refreshes the page).
- // 📜 https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
- //
- // 🐨 get the value from the username input (using whichever method
- // you prefer from the options mentioned in the instructions)
- // 💰 For example: event.target.elements[0].value
- // 🐨 Call `onSubmitUsername` with the value of the input
-
- // 🐨 add the onSubmit handler to the
- )
-}
-
-function App() {
- const onSubmitUsername = username => alert(`You entered: ${username}`)
- return
-}
-
-export default App
diff --git a/src/exercise/06.md b/src/exercise/06.md
index 5960fd1cb..92d436ae6 100644
--- a/src/exercise/06.md
+++ b/src/exercise/06.md
@@ -1,4 +1,4 @@
-# Forms
+# Styling
## 📝 Your Notes
@@ -6,150 +6,200 @@ Elaborate on your learnings here in `src/exercise/06.md`
## Background
-In React, there actually aren't a ton of things you have to learn to interact
-with forms beyond what you can do with regular DOM APIs and JavaScript. Which I
-think is pretty awesome.
+There are two primary ways to style react components
-You can attach a submit handler to a form element with the `onSubmit` prop. This
-will be called with the submit event which has a `target`. That `target` is a
-reference to the `