Year #2, Week #20 đť đ
New stuff we learned this week: đ¤
Conditional Rendering in React
- In React, itâs common to sometimes want to render a component, and other times skip rendering. There are several ways to accomplish this, some are better than others, and there are also a couple common pitfalls to avoid.
- Imagine you had this component, which rendered a headline, and then might also
have a secondary headline, if a
user
prop is present:
const Header: React.FC<{ user?: string }> = ({ user }) => {
return (
<header>
<h1>Bob's Discount Hunting Shack</h1>
<h2>{user}</h2>
</header>
);
};
- the above code sorta works, but when
user
is missing (itâs optional, after all), the HTML still renders out an empty<h2></h2>
element, which is funky, and feels a little sad, and could cause problems. We can do better. Letâs instead introduce a conditional. But first, hereâs an approach that wonât even compile:
const Header: React.FC<{ user?: string }> = ({ user }) => {
return (
<header>
<h1>Bob's Discount Hunting Shack</h1>
{if (user) { <h2>{user}</h2 >}} đ¨đ¨ WONT COMPILE
</header>
);
};
- you see, weâre not allowed to us
if
statements inside JSX, we can only useexpressions
. The difference between a statement and expression is a little squirrelly, but the simple way to think about it is that an expression returns a value, and a statement doesnât. Remember, JSX is just syntactic sugar over a bunch of calls soReact.createElement()
, so we canât do complex logic. The above code doesnât work for the same reason you canât put anif
statement into a normal function call expression:
function add3(x: number): number {
return x + 3;
}
let name = "jared"
// đ¨ NO BUENO, YOU CAN'T DO THIS EITHER!
add3(if (name === "jared") { 5 } else { 6});
- OK, so we canât use
if/else
statements, but we can use logical operators and ternaries. First letâs look at ternaries, if youâre going to render using a ternary, itâs straightforward if you have something you want to render no matter:
const Header: React.FC<{ user?: string }> = ({ user }) => {
return (
<header>
<h1>Bob's Discount Hunting Shack</h1>
<h2>User: {user ? user : "not logged in"}</h2>
</header>
);
};
- but what if we want to render nothing? In that case, itâs good to know
that
null
, andundefined
, andfalse
, and the empty string""
are all valid things that can be rendered inside of a React component, and they all produce no HTML:
// â
this component is VALID, and renders an empty div
const EmptyDiv: React.FC = () => {
return (
<div>
{null}
{undefined}
{false}
{""}
</div>
);
};
- armed with this knowledge, we can improve our original example, and only
render the
<h2>
when we have a user:
const Header: React.FC<{ user?: string }> = ({ user }) => {
return (
<header>
<h1>Bob's Discount Hunting Shack</h1>
{user ? <h2>{user}</h2> : null}
</header>
);
};
- we could also use the nullish coalescing operator or one of the other logical operators:
const Header: React.FC<{ user?: string }> = ({ user }) => {
return (
<header>
<h1>Bob's Discount Hunting Shack</h1>
{user ?? <h2>{user}</h2>}
</header>
);
};
- one pitfall to be aware of is that not all falsy values render nothing
in react. Specifically, numbers can be rendered in a react component, and
the number 0 will render, even though itâs falsy. That means that code like
below is a common source of bugs. This component will render a weird
0
digit when there are no specials:
// đ¨ Line 5 below has a bug
const Mart: React.FC<{ specials: [string] }> = ({ specials }) => (
<div>
<h1>Sally's Stuff Mart</h1>
{specials.length && <h2>We have specials!</h2>}
</div>
);
- the way to fix that bug is to explicitly ensure the left-hand-side will always
be a boolean, by changing it to:
{specials.length !== 0 && [...etc]}
Homework Plan
- 1 days review all flashcards (in your app)
- 2 days Flashcard App assignment
- 1 day Akron Snowmen assignment
- 1 day Personal Project assignment
- 1 day touch typing practice
- 4 days Execute Program homework
Flashcard App Assignment
- This week weâre going to add the ability to delete a flashcard from your app.
- In order to delete a card, weâre going to need some way to uniquely identify
each card. Right now we donât have that, other than by the index of the
array of cards. But that feels a little scary. So weâre going to start by
adding an
id
property to each card. - there are a lot of ways to assign ids to things in computer-land, the most intuitive and maybe easiest is just to use integers. However, this has some drawbacks, like you have to keep track of which integer youâre on when adding a new one, you can end up with âholesâ in your id scheme, and it can be a security risk in some contexts. Years ago almost everything was integer based for identification, but you rarely see that today. So weâre going to be modern and not use integers.
- instead of integers weâll use UUIDs, as we went over in class
- to start, make sure you addressed all my feedback on your MR from last week, then merge.
- then, connect to your flashcards repo, switch to master, pull, delete your old branch, and make a new branch
- run an npm command to install a new dependency called
uuid
. If you canât remember how check out this old new stuff - after you install
uuid
do agit diff
and see if you can understand why both yourpackage.json
and yourpackage-lock.json
file changed. Do you understand the basic idea of why you have changes in both files now? - where would you expect that
npm
installed the new package? Verify your guess by usingls
andcat
to find the files for the package. (You wonât be able to see them through vscode, because I some files from being accessed by vscode, if you recall.) - the
uuid
package (if you were to look on itâs Github readme, or npm documentation, which you canât likely read because itâs probably blocked for you), allows you to create UUIDs from code in javascript/typescript, but it also exposes a CLI command to get a UUID from the shell. Do you remember where npm puts âexecutableâ (or âbinaryâ) files? Thatâs right! In the./node_modules/.bin
dir.ls
that dir to see if you can see that there is an executabe file calleduuid
. Do you see it? - lets experiment with getting a UUID from the shell by typing
./node_modules/.bin/uuid
go ahead, and try it. You should see a uuid likecad5a6c5-3d9f-45c8-924d-fe2cbbd153db
printed to your terminal. Go ahead, try it a few more times! You could do it for the rest of your life and never get the same uuid. Cool, huh? - one more thing as an ânpm learning momentâ â the
npx
command has two purposes in life (which is confusing, but heyâŚ). One of those purposes is as a shortcut to invoking binaries in the./node_modules/.bin
dir. So, for a less gnarly looking command, try this instead:npx uuid
and you should see that you also get a uuid to your terminal. - OK, npm learning digression is over, back to our task. We want to get one of
these UUIDs added to EVERY one of our cards. Now, we could just set there with
our terminal open and generate one after another, copy them and paste them
into our
card-data.json
file, but that sounds like a BIG pain, right? Weâre better than that. Instead, weâre going to create a temporary helper script that weâll use to add UUIDs to our flashcards, and when itâs done itâs mission, weâll delete it and never commit it to our repo. I do this kind of thing all the time, to prevent error-prone and tedious manual labor that computers are better suited for than humans. - create a file in the root of your project called
add-uuids.js
â weâll do this in straight up javascript to make it so we donât have to fiddle with compiling. But, so we get some help from typescript, add this as the very first line in the project:
// @ts-check
- then, weâre going to import the
uuid
package, but since this is the wild-wild west, not Typescript, weâll use the oldcommonjs
style, so add this as line 2:
// @ts-check
const uuid = require("uuid");
- next, weâll call the âversion 4â function, which is the normal version of
getting a uuid, by adding this code:
console.log(uuid.v4());
on line 3 or 4. - test your script by calling
node ./add-uuids.js
and you should see a uuid logged out to the screen. - now for the part where youâre on your own: youâre going to make this script
add a new uuid to every flashcard you have in your
card-data.json
file. Please start by very slowly and carefully reading the ENTIRE hints/requirements section below: - requirements/hints:
- use
cp
or vscodeâs GUI to make a backup of yourcard-data.json
file in case you horribly screw this up. - do not try to write the whole script in one go and then test if it works,
instead, go line by line,
console.log
-ing each step making sure the script is doing what you want it to do. I would try to get it all the way to the part where it has added UUIDs to each card, and then I would just log the data without writing it back to the file. Then, only if everything looked hunky dory, would I finally use the script to write updated data to the file. - each time you do a little more, and want to console.log your progress,
youâll be invoking the file from the shell by doing
node ./add-uuids.js
- youâll need to import the
fs
module, by addingconst fs = require("fs");
- youâll need to use
JSON.parse()
to turn the file contents into an array (after you read the file contents into a string) - after you get the card data into an array, youâll need to
.forEach
or loop through each card adding a new property calledid
which has the value of callinguuid.v4()
. - after you modify the array, adding an id to each card, youâll need to stringify the array back into a string with JSON.stringify and then write the file back to disk, although I would do a âpractice runâ of just logging out the modified data one last time before I wrote the file to the disk.
- if you mess up majorly and corrupt your
card-data.json
file, donât worry, just restore the file you made as a backup (you did make a backup, right?) - to make
JSON.stringify()
create pretty-printed JSON, you need to passnull
and2
as the last two args, like this:JSON.stringify(someVariable, null, 2)
â if you donât do this, yourcard-data.json
file will all be one line. This isnât a huge deal though, as itâs probable that if you view the file with vscode that prettier will fix it up for you. But itâs a good trick to know, nonetheless.
- use
- when youâre all set, double-check that your
card-data.json
file looks like valid json, and that each card has a"id":
property which is a UUID. - then, you can delete the script you made
- make a commit â it should only be the
package.json
andpackage-lock.json
file, but thatâs OK.
Flashcards Homework, pt. 2
- connect in vscode to your flashcards app
- in part 1 of the homework, we ensured that every existing card in your app
had a uniqe
id
property. Next weâre going to modify the server code so that every card you add ALSO gets an id. - there are two ways we could go about this â a) we could assign the new card a uuid in the browser, and send it to the server â or b) we could do the opposite, and create it directly on the server. There are pros and cons to both methods, but for our use case, thereâs no real benefit to creating the idâs on the client (browser), so weâll keep thinks easy and go with option b), create them on the server.
- So, modify your
server/index.ts
file to import theuuid
package, and add a new id to each card that is created. Note: back now in Typescript, youâll import the module using ES Module syntax, like normal, not how we did in our throwaway javascript script, so it will look like this:
import uuid from "uuid";
uuid.v4(); // > 5a92a665-6865-4c04-9be0-fdade72a7c57
- once youâve got it working, check by adding a new card from your browser, and
verify by peeking at your
card-data.json
file that the new card was added with a new UUID. - commit your work
- for our final task of the week, weâre going to add a âdeleteâ button on each
card so that you can delete a flashcard from the browser. Read all of the
following hints/requirements before tackling the feature.
- when the delete button is clicked, you should use
fetch()
to send aDELETE
request to a path like:/cards/<uuid>
, i.e.:cards/5a92a665-6865-4c04-9be0-fdade72a7c57
. - in your server code, you will need to detect when the HTTP Method is
DELETE
and then somehow extract the id from the url. - once youâve got the url extracted from the full url path, use that to find
and remove the card with that id, saving the card data back to disk
without the deleted card. (Hint: probably the easiest way to do this
would be to use
Array.filter()
to create a filtered version of the array containing only the cards whose Ids do not match. But if you want to be more fancy, you could do it more efficiently withArray.slice()
) - after youâve successfully deleted a card, the server should send bacy a â204 No contentâ response to the client.
- if the client (or a hacker!) tries to delete a card that does not exist,
the server should somehow figure this out, and return a
404 Not Found
error. - Extra Credit: ⨠Prevent people from accidentally deleting a card with
a stray click by first prompting them to confirm that they really want
to delete the card before sending the
DELETE
request. (Hint: prompt them, cough coughâŚ)
- when the delete button is clicked, you should use
Personal Project Homework
- Refer to your work plan you created a few weeks ago, and select the next item on your list. If youâre ahead or behind of where you thought you would be, make any modifications you think appropriate, then Slack me your goal for this week by WEDNESDAY at 9AM!!!! đ đ
- Make sure youâve addressed all of my feedback from last week, merge your MR, connect with vscode, pull from origin, and create a new branch.
- Implement the feature or chunk of work you planned.
- When you think youâre done, check things like:
- did you leave in any
console.log()
s? - does it look good at all screen sizes?
- do your storybook stories work and cover your components (if youâre using storybook)
- are your components and variables named well?
- is there anything you want to clean up, refactor, or DRY up before you submit?
- did you leave in any
- when youâre happy with the code, build your site, submit a MR, and Slack both the URLs.
- after I review, address any feedback I give you.
Akron Snowmen Homework
- spend a few minutes carefully reviewing the state of the Akron Snowmen website
at this url, where Iâve built
the site as it exists in
upstream
. Jot down one or two small things that seem like they could/should be improved. Things likeâŚ- any small spacing tweaks where things just donât look exactly right
- view the site at different screen sizes, are there any points where we could dial in the media queries just a bit to improve things?
- if you see something big that needs changing, maybe post it in slack, but we are looking for small wins for this assignment
- is there any text that needs improving?
- links that donât point to the right thing?
- or, instead of looking just at the website, scan through the code â is there anything that you want to clean up or DRY up a bit? Rename any props or variables for greater clarity?
- or anything else you find.
- pick 1-3 of these little things (depending on their size, I donât want this assignment to take you more than 30-45 minutes max) that you want to fix
- connect to your AS repo in vscode, switch over to master, pull from
upstream
, and create a new branch - for each thing you want to fix, fix the thing and commit your work once for each task.
- when youâre done, build the site, submit a MR, and post both URLs in Slack.
- THEN, check if any other students have already submitted their Mrs for this task. I want every student to review and comment on every MR for this task. Look carefully at the code changes, and test the outcome by viewing the posted built URL. Give any constructive feedback you have, but of course, be kind and encouraging. But also donât be afraid to nicely call out something that doesnât seem right.