Skip to content

Commit

Permalink
custom hooks and dev tools
Browse files Browse the repository at this point in the history
  • Loading branch information
btholt committed Sep 19, 2024
1 parent 2f554b4 commit 45fc577
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 5 deletions.
13 changes: 10 additions & 3 deletions lessons/04-core-react-concepts/B-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ const [pizzaSize, setPizzaSize] = useState("medium");
[…]
</select>;

// replace the div surrounding the radio buttons
<div onChange={(e) => setPizzaSize(e.target.value)}>[…]</div>;
// add to all the radio buttons
onChange={(e) => setPizzaSize(e.target.value)}
```

- This is called a hook. Other frameworks like Vue have adopted it as well.
Expand All @@ -113,7 +113,14 @@ const [pizzaSize, setPizzaSize] = useState("medium");
- Similar to above. We're using `onChange` and `onBlur` because it makes it more accessible.

> 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.
> 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
> 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.
Another side note: event bubbling works in React works just like you would expect. In theory you can have mega event handler in React but the lint rules and React's dev tools get noisy about it if you do it that way so I tend to just follow their recommendation.

```javascript
// you could replace the div surrounding the radio buttons and remove all the onChange handlers
<div onChange={(e) => setPizzaSize(e.target.value)}>[…]</div>
```

> 🏁 [Click here to see the state of the project up until now: 04-hooks][step]
Expand Down
9 changes: 7 additions & 2 deletions lessons/04-core-react-concepts/C-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ async function fetchPizzaTypes() {

// replace the options
{
pizzaTypes.map((pizza) => <option value={pizza.id}>{pizza.name}</option>);
pizzaTypes.map((pizza) => (
<option key={pizza.id} value={pizza.id}>
{pizza.name}
</option>
));
}

// replace <Pizza /> and button at the end
Expand All @@ -58,7 +62,8 @@ async function fetchPizzaTypes() {
- 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.
- 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.
- 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.
- The `key` portion is an interesting one. When React renders arrays of things, it doesn't know the difference between something is new and something is just being re-ordered in the array (think like changing the sorting of a results list, like price high-to-low and then priced low-to-high). Because of this, if you don't tell React how to handle those situations, it just tears it all down and re-renders everything anew. This can cause unnecessary slowness on devices. This is what key is for. Key tells React "this is a simple identifier of what this component is". If React sees you just moved a key to a different order, it will keep the component tree. So key here is to associate the key to something unique about that component. 99/100 this is a database ID of some variety. _Don't_ use the index of the array as that just isn't right unless the array is literally is never going to change order.

> 🏁 [Click here to see the state of the project up until now: 05-effects][step]
[step]: https://github.com/btholt/citr-v8-project/tree/master/05-effects
[step]: https://github.com/btholt/citr-v9-project/tree/master/05-effects
25 changes: 25 additions & 0 deletions lessons/04-core-react-concepts/D-dev-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
description: "An essential tool in any React developer's toolbox is the official React Dev Tools extension. Brian shows you how to install and use them."
---

React has some really great tools to enhance your developer experience. We'll go over a few of them here.

## `NODE_ENV=development`

React already has a lot of developer conveniences built into it out of the box. What's better is that they automatically strip it out when you compile your code for production.

So how do you get the debugging conveniences then? Well, if you're using Vite.js, it will compile your development server with an environment variable of `NODE_ENV=development` and then when you run `vite build` it will automatically change that to `NODE_ENV=production` which is how all the extra weight gets stripped out.

Why is it important that we strip the debug stuff out? The dev bundle of React is quite a bit bigger and quite a bit slower than the production build. Make sure you're compiling with the correct environmental variables or your users will suffer.

## Strict Mode

React has a new strict mode. If you wrap your app in `<StrictMode></StrictMode>` it will give you additional warnings about things you shouldn't be doing. I'm not teaching you anything that would trip warnings from `StrictMode` but it's good to keep your team in line and not using legacy features or things that will be soon be deprecated.

Be aware that `StrictMode` continually double-renders your components and will run effects twice. It does this catch subtle bugs where your app will change between renders when it's not meant to. It can be helpful, but to be honest, once you learn to write React the correct way you'll nearly never hit that sort of bug.

## Dev Tools

React has wonderful dev tools that the core team maintains. They're available for both Chromium-based browsers and Firefox. They let you do several things like explore your React app like a DOM tree, modify state and props on the fly to test things out, tease out performance problems, and programtically manipulate components. Definitely worth downloading now. [See here][dev-tools] for links.

[dev-tools]: https://react.dev/learn/react-developer-tools
98 changes: 98 additions & 0 deletions lessons/04-core-react-concepts/E-custom-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
One thing that's pretty special about hooks is their composability. You can use hooks to make other hooks! People tend to call these custom hooks. There are even people who go as far to say "never make an API request in a component, always do it in a hook." I don't know if I'm as hardcore as that but I see the logic in it. If you make a custom hook for those sorts of things they become individually testable and do a good job to separate your display of data and your logic to acquire data. I'm more in the camp of make custom hooks for either complicated logic or reusable logic, but for simple cases it's okay to keep things simple.

Okay, so we want to add a "Pizza of the Day" banner at the bottom of our page. This necessitates calling a special API to get the pizza of the day (which should change every day based on your computer's time.) Let's first write the component that's going to use it.

Make a file called PizzaOftheDay.jsx

```javascript
import { usePizzaOfTheDay } from "./usePizzaOfTheDay";

// feel free to change en-US / USD to your locale
const intl = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});

const PizzaOfTheDay = () => {
const pizzaOfTheDay = usePizzaOfTheDay();

if (!pizzaOfTheDay) {
return <div>Loading...</div>;
}

return (
<div>
<div className="pizza-of-the-day-info">
<h2>Pizza of the Day</h2>
<h3>{pizzaOfTheDay.name}</h3>
<p>{pizzaOfTheDay.description}</p>
</div>
<img
className="pizza-of-the-day-image"
src={pizzaOfTheDay.image}
alt={pizzaOfTheDay.name}
/>
<p className="pizza-of-the-day-price">
From: <span>{intl.format(pizzaOfTheDay.sizes.S)}</span>
</p>
</div>
);
};

export default PizzaOfTheDay;
```

- The cool part here is the `usePizzaOfTheDay()`. We now just get to rely on that this going to provide us with the pizza of the day from within the black box of the hook working.

Okay, let's go make the hook! Make a file called usePizzaOfTheDay.jsx (you could call this .js instead of jsx but in a React project I just use JSX for all "React-y" things)

```javascript
import { useState, useEffect } from "react";

export const usePizzaOfTheDay = () => {
const [pizzaOfTheDay, setPizzaOfTheDay] = useState(null);

useEffect(() => {
async function fetchPizzaOfTheDay() {
const response = await fetch("/api/pizza-of-the-day");
const data = await response.json();
setPizzaOfTheDay(data);
}

fetchPizzaOfTheDay();
}, []);

return pizzaOfTheDay;
};
```

- This looks like what you'd see at the top of a component, right? Now it's just encapsulated as a hook which makes it easy to test by itself! 🎉
- We can use `useState`, `useEffect`, or any hook we want here! We can even use other custom hooks!

Lastly, let's go add this to App.jsx so our component renders.

```javascript
// import at top
import PizzaOfTheDay from "./PizzaOfTheDay";

// under <Order />
<PizzaOfTheDay />;
```

You should now see the new component which uses our new hook at the bottom!

Let's add one more fun debugging technique made especially for custom hooks. Put this in usePizzaOfTheDay.jsx:

```javascript
// import useDebugValue
import { useState, useEffect, useDebugValue } from "react";

// add under the hook being used in the render function
useDebugValue(pizzaOfTheDay ? `${pizzaOfTheDay.name}` : "Loading...");
```

Now open your React Dev Tools and inspect our PizzaOfTheDay component. You'll see our debug value there. This is helpful when you have _lots_ of custom hooks and in particular lots of reused custom hooks that have differing values. It can help at a glance to discern which hook has which data inside of it.

> 🏁 [Click here to see the state of the project up until now: 05-custom-hooks][step]
[step]: https://github.com/btholt/citr-v9-project/tree/master/05-custom-hooks

0 comments on commit 45fc577

Please sign in to comment.