Skip to content

Commit 2f554b4

Browse files
committed
work from the plane
1 parent de537bb commit 2f554b4

File tree

6 files changed

+330
-1
lines changed

6 files changed

+330
-1
lines changed

components/layout.js

+8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ function Layout({ children }) {
3131
linkedin={courseInfo.social.linkedin}
3232
/>
3333
</div>
34+
<script async defer src="https://a.holt.courses/latest.js"></script>
35+
<noscript>
36+
<img
37+
src="https://a.holt.courses/noscript.gif"
38+
alt=""
39+
referrerPolicy="no-referrer-when-downgrade"
40+
/>
41+
</noscript>
3442
</HeaderProvider>
3543
</CourseInfoProvider>
3644
);

lessons/01-welcome/A-intro.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
- npm
1010
- JSX
1111
- Hooks
12+
- React Dev Tools
1213
- Forms
1314
- Effects
1415
- Gets
1516
- Posts
1617
- Async Operations
17-
- React Dev Tools
1818
- Custom Hooks
1919
- Component Composition / Single Purpose Components
2020
- Other Useful Hooks
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
---
2+
title: "JSX"
3+
description: "JSX is an essential part of writing React. Brian teaches you to leverage your newfound React knowledge and make it much easier to read with JSX"
4+
---
5+
6+
So far we've been writing React without JSX, something that I don't know anyone that actually does with their apps. _Everyone_ uses JSX. I show you this way so what JSX is actually doing is demystified to you. It doesn't do hardly anything. It just makes your code a bit more readable.
7+
8+
If I write `React.createElement("h1", { id: "main-title" }, "My Website");`, what am I actually trying to have rendered out? `<h1 id="main-title">My Website</h1>`, right? What JSX tries to do is to shortcut this translation layer in your brain so you can just write what you mean.
9+
10+
Make a new file called Pizza.jsx.
11+
12+
> Make sure you call it `.jsx` and not `.js`. Vite won't do JSX transpilation if it's not named with a JSX file extension.
13+
14+
```javascript
15+
const Pizza = (props) => {
16+
return (
17+
<div>
18+
<h1>{props.name}</h1>
19+
<p>{props.description}</p>
20+
</div>
21+
);
22+
};
23+
24+
export default Pizza;
25+
```
26+
27+
I don't know about you, but I find this far more readable. And if it feels uncomfortable to you to introduce HTML into your JavaScript, I invite you to give it a shot until the end of the workshop. By then it should feel a bit more comfortable. And you can always go back to the old way.
28+
29+
However, now you know _what_ JSX is doing for you. It's just translating those HTML tags into `React.createElement` calls. _That's it._ Really. No more magic here. JSX does nothing else. Many people who learn React don't learn this.
30+
31+
Notice the strange `{props.name}` syntax: this is how you output JavaScript expressions in JSX. An expression is anything that can be the right side of an assignment operator in JavaScript, e.g. `const x = <anything that can go here>`. If you take away the `{}` it will literally output `props.name` to the DOM.
32+
33+
> Notice we don't have to do `import React from 'react'` here like we used to. The latest version of JSX handles that for you so you only need to explicitly import the React package when you need to use something from it; otherwise feel free to do JSX without having to import React!
34+
35+
So now JSX is demystified a bit, let's go convert App.js.
36+
37+
```javascript
38+
// rename the file App.jsx
39+
// delete the React import
40+
import { createRoot } from "react-dom/client";
41+
import Pizza from "./Pizza";
42+
43+
// delete the Pizza component
44+
45+
const App = () => {
46+
return (
47+
<div>
48+
<h1>Padre Gino's Pizza – Order Now</h1>
49+
<Pizza name="Pepperoni" description="Mozzarella Cheese, Pepperoni" />
50+
<Pizza
51+
name="The Hawaiian Pizza"
52+
description="Sliced Ham, Pineapple, Mozzarella Cheese"
53+
/>
54+
<Pizza
55+
name="The Big Meat Pizza"
56+
description="Bacon, Pepperoni, Italian Sausage, Chorizo Sausage"
57+
/>
58+
</div>
59+
);
60+
};
61+
62+
const container = document.getElementById("root");
63+
const root = createRoot(container);
64+
root.render(React.createElement(App));
65+
```
66+
67+
Also head over to index.html and change the script tag
68+
69+
```html
70+
<script type="module" src="./src/App.jsx"></script>
71+
```
72+
73+
Notice we have Pizza as a component. Notice that the `P` in `Pizza` is capitalized. It _must_ be. If you make it lowercase, it will try to have `pizza` as a web component and not a React component.
74+
75+
We now pass props down as we add attributes to an HTML tag. Pretty cool.
76+
77+
## The API / Image Server
78+
79+
For this course we will use a little Fastify server I built for you. It's in the [api][api]
80+
directory. We are going to use Vite.js to proxy to this API server. This is a useful trick to do for local development if you have a separate frontend in a backend. Normally you'd have something like NGINX routing traffic to separate frontend and backend servers. For now we'll just use Vite.
81+
82+
Add this to you vite.config.js
83+
84+
```javascript
85+
// replace export
86+
export default defineConfig({
87+
server: {
88+
proxy: {
89+
"/api": {
90+
target: "http://localhost:3000",
91+
changeOrigin: true,
92+
},
93+
"/public": {
94+
target: "http://localhost:3000",
95+
changeOrigin: true,
96+
},
97+
},
98+
},
99+
plugins: [react()],
100+
});
101+
```
102+
103+
Now run your API server by running api/server.js by running `node api/server.js`. Note that this server is _outside_ your project directory. If you're in the project directory, you'll have to run `node ../api/server.js`. You need both servers running at the same time. Your Vite server will intercept `api` and `public` calls and reroute them to your API server!
104+
105+
Now let's add images to our Pizza.
106+
107+
```javascript
108+
// return inside Pizza, inside div, under <p>
109+
<img src={props.image} alt={props.name} />
110+
```
111+
112+
Now in App.jsx
113+
114+
```javascript
115+
// add to first Pizza
116+
image={"/public/pizzas/pepperoni.webp"}
117+
118+
// add to second Pizza
119+
image={"/public/pizzas/hawaiian.webp"}
120+
121+
// add to third Pizza
122+
image={"/public/pizzas/big_meat.webp"}
123+
```
124+
125+
And now you should have images!
126+
127+
> 🏁 [Click here to see the state of the project up until now: 03-jsx][step]
128+
129+
[airbnb]: https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb
130+
[standard]: https://standardjs.com/
131+
[step]: https://github.com/btholt/citr-v9-project/tree/master/03-jsx
132+
[api]: https://github.com/btholt/citr-v9-project/tree/main/api
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
---
2+
description: "React manages view state through a mechanism called hooks. Brian teaches you how to use them as you build components."
3+
---
4+
5+
Okay, so now we want to make it so people can add pizzas to their order. We need a little form that allows them to select the pizza and the size. Create a new file called Order.jsx and add the following:
6+
7+
```javascript
8+
import Pizza from "./Pizza";
9+
10+
export default function Order() {
11+
const pizzaType = "pepperoni";
12+
const pizzaSize = "medium";
13+
return (
14+
<div className="order">
15+
<form>
16+
<div>
17+
<label htmlFor="pizza-type">Pizza Type</label>
18+
<select name="pizza-type" value={pizzaType}>
19+
<option value="pepperoni">The Pepperoni Pizza</option>
20+
<option value="hawaiian">The Hawaiian Pizza</option>
21+
<option value="big_meat">The Big Meat Pizza</option>
22+
</select>
23+
</div>
24+
<div>
25+
<label htmlFor="pizza-size">Pizza Type</label>
26+
<div>
27+
<input
28+
checked={pizzaSize === "small"}
29+
type="radio"
30+
name="pizza-size"
31+
value="small"
32+
/>
33+
<label>Small</label>
34+
<input
35+
checked={pizzaSize === "medium"}
36+
type="radio"
37+
name="pizza-size"
38+
value="medium"
39+
/>
40+
<label>Medium</label>
41+
<input
42+
checked={pizzaSize === "large"}
43+
type="radio"
44+
name="pizza-size"
45+
value="large"
46+
/>
47+
<label>Large</label>
48+
</div>
49+
</div>
50+
<Pizza
51+
name="Pepperoni"
52+
description="Mozzarella Cheese, Pepperoni"
53+
image="/public/pizzas/pepperoni.webp"
54+
/>
55+
<button type="submit">Add to Cart</button>
56+
</form>
57+
</div>
58+
);
59+
}
60+
```
61+
62+
Now add it to your App.jsx:
63+
64+
```javascript
65+
// delete Pizza import, and add Order
66+
import Order from "./Order";
67+
68+
// in App.jsx, replace all the Pizzas
69+
<Order />;
70+
```
71+
72+
> 🚨 You'll have some errors in the console, that's okay.
73+
74+
Now navigate to [http://localhost:5173/]() and see that you have two inputs, one for the pizza type and a set of radio buttons for the size. Try and select something with the inputs. You'll see that you can't modify them. Why? Let's think about how React works: when you interact with the inputs, React detects that a DOM event happens. When that happens, React thinks _something_ may have changed so it runs a re-render. Providing your render functions are fast, this is a very quick operation. It then diffs what's currently there and what its render pass came up with. It then updates the minimum amount of DOM necessary.
75+
76+
Notice we're using `className` instead of `class` on the HTML element for CSS classes. This is because `class` is a reserved word in JS and JSX is still just JS. So instead they opted to use `className` which is the [name of the JS API][js-api] for interacting with class names.
77+
78+
Like `className`, `htmlFor` is used because `for` is a reserved word in JS.
79+
80+
So if we type in our input and it re-renders, what gets out in the `select` or radio button tags? Well, its value is tied to `pizzaType` and `pizzaSize` and nothing changed those, so they remain the same. In other words, two way data binding is _not_ free in React. I say this is a feature because it makes you explicit on how you handle your data. Let's go make it work.
81+
82+
```javascript
83+
// in Order.jsx
84+
import { useState } from "react";
85+
86+
// pizzaType and pizzaSize location
87+
const [pizzaType, setPizzaType] = useState("pepperoni");
88+
const [pizzaSize, setPizzaSize] = useState("medium");
89+
90+
// replace input
91+
<select
92+
onChange={(e) => setPizzaType(e.target.value)}
93+
name="pizza-type"
94+
value={pizzaType}
95+
>
96+
[…]
97+
</select>;
98+
99+
// replace the div surrounding the radio buttons
100+
<div onChange={(e) => setPizzaSize(e.target.value)}>[…]</div>;
101+
```
102+
103+
- This is called a hook. Other frameworks like Vue have adopted it as well.
104+
- A hook called such (in my head) because it's a hook that gets caught every time the render function gets called. Because the hooks get called in the same order every single time, they'll always point to the same piece of state. Because of that they can be stateful: you can keep pieces of mutable state using hooks and then modify them later using their provided updater functions.
105+
- An _absolutely key_ concept for you to grasp is hooks rely on this strict ordering. As such, **do not put hooks inside if statements or loops**. If you do, you'll have insane bugs that involve `useState` returning _the wrong state_. If you see `useState` returning the wrong piece of state, this is likely what you did. Every hook must run every time in the same order.
106+
- The argument given to `useState` is the default value. In our case, we could give it `""` as our default value to make the user have to select something first but in our case we want to default to pepperoni pizza and medium size.
107+
- `useState` returns to us an array with two things in it: the current value of that state and a function to update that state. We're using a feature of JavaScript called destructuring to get both of those things out of the array.
108+
- We use the `setPizzaType` / `setPizzaSize` function in the `onChange` attribute of the input. Every time the input is typed into, it's going to call that functions which call `setPizzaType` and `setPizzaSize` with what has been typed into the input or what has been selected or what has been clicked on. When `setPizzaType` and `setPizzaSize` are called, React knows that its state has been modified and kicks off a re-render.
109+
- You can make your own custom hooks; `useState` is just one of many.
110+
- Historically, React has been written using `class`es with state being on the instance of the component. This is still a supported pattern in React. We'll see how to do it later.
111+
- We could have put an onChange handler on each of the radio buttons. However event bubbling works the same in React as it does in the normal DOM and we could put it directly on the div that encapsulates all the radio buttons and just have to do it once.
112+
- You can use `useState` as many times as you need for various pieces of state! Again, this is why ordering is important because React relies on `useState` to be called in strictly the same order every time so it can give you the same piece of state.
113+
- Similar to above. We're using `onChange` and `onBlur` because it makes it more accessible.
114+
115+
> I'm showing you how to do a "controlled form" in that we're using hooks to control each part of the form. In reality, it'd be better to leave these _uncontrolled_ (aka don't set the value) and wrap the whole thing in a form. Then we can listen for submit events and use that event to gather info off the form. This is less code and less burdensome to write. If you have a standard form sort of thing to write, do that as an uncontrolled form. If you need to do dynamic validation, react to a user typing a la typeahead (functionality that provides real-time suggestions), or something of the ilk, then a controlled input is perfect, otherwise stick to uncontrolled.
116+
> Also what's new in React is called a "form action" that is considered unstable. In the future you will just add `<form action="blah">[…]</form>` and a form action will handle the entire form for you. This will then dovetai
117+
118+
> 🏁 [Click here to see the state of the project up until now: 04-hooks][step]
119+
120+
[babel]: https://babeljs.io/
121+
[step]: https://github.com/btholt/citr-v8-project/tree/master/04-hooks
122+
[js-api]: https://developer.mozilla.org/en-US/docs/Web/API/Element/className
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
description: "useEffect is a critical hook for React, allowing developers to do asynchronous actions like making HTTP requests"
3+
---
4+
5+
We have enough of an app to start making some API requests now. We want the app to request an initial set of pets on initial load of the page. So let's make that happen using a special hook called `useEffect`. `useEffect` allows you to say do a render of this component first so the user can see _something_ then as soon as the render is done, _then_ do something (the something here being an effect). In our case, we want the user to see our UI first then we want to make a request to the API so we can initialize a list of pizzas.
6+
7+
> Make sure you have both your Vite dev server running _and_ your API server running. Both.
8+
9+
Let's refactor Order.jsx
10+
11+
```javascript
12+
// change import at top
13+
import { useEffect, useState } from "react";
14+
import Pizza from "./Pizza";
15+
16+
// outside of the render function
17+
// feel free to change en-US / USD to your locale
18+
const intl = new Intl.NumberFormat("en-US", {
19+
style: "currency",
20+
currency: "USD",
21+
});
22+
23+
// add to the other useStates inside component at top
24+
const [pizzaTypes, setPizzaTypes] = useState([]);
25+
const [loading, setLoading] = useState(true);
26+
27+
let price, selectedPizza;
28+
if (!loading) {
29+
selectedPizza = pizzaTypes.find((pizza) => pizzaType === pizza.id);
30+
price = intl.format(
31+
selectedPizza.sizes ? selectedPizza.sizes[pizzaSize] : ""
32+
);
33+
}
34+
35+
useEffect(() => {
36+
fetchPizzaTypes();
37+
}, []);
38+
39+
async function fetchPizzaTypes() {
40+
await new Promise((resolve) => setTimeout(resolve, 3000));
41+
42+
const pizzasRes = await fetch("/api/pizzas");
43+
const pizzasJson = await pizzasRes.json();
44+
setPizzaTypes(pizzasJson);
45+
setLoading(false);
46+
}
47+
48+
// replace the options
49+
{
50+
pizzaTypes.map((pizza) => <option value={pizza.id}>{pizza.name}</option>);
51+
}
52+
53+
// replace <Pizza /> and button at the end
54+
```
55+
56+
- We're taking advantage of closures here that if we define the requestPets function _inside_ of the render that it will have access to that scope and can use all the hooks there.
57+
- We put all the logic for fetching pizza types in an async function to make it more readable. You can't make the function provided to useEffect async.
58+
- the `[]` at the end of the useEffect is where you declare your data dependencies. React wants to know _when_ to run that effect again. You don't give it data dependencies, it assumes any time any hook changes that you should run the effect again. This is bad because that would mean any time setPets gets called it'd re-run render and all the hooks again. See a problem there? It'd run infinitely since requestPets calls setPets.
59+
- You can instead provide which hooks to watch for changes for. In our case, we actually only want it to run once, on creation of the component, and then to not run that effect again. (we'll do searching later via clicking the submit button) You can accomplish this only-run-on-creation by providing an empty array.
60+
- We're using a loading flag to only display data once it's ready. We'll use TanStack Query in a bit to make this code look cleaner. But this is how you do conditional showing/hiding of components in React.
61+
62+
> 🏁 [Click here to see the state of the project up until now: 05-effects][step]
63+
64+
[step]: https://github.com/btholt/citr-v8-project/tree/master/05-effects

next.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* @type {import('next').NextConfig}
3+
*/
14
const config = {
25
output: "export",
36
};

0 commit comments

Comments
 (0)