Year #2, Week #19 💻 🚦

New stuff we learned this week: 🤔

Javascript Dates 📅

  • Working with Dates & Times in Javascript is done (for now) through the global Date object.
  • at it’s most fundamental level, a Javascript date is the number of milliseconds elapsed since midnight, Jan 1, 1970. This is related to what is called the unix epoch, a system of counting time (in seconds, not milliseconds) since the same moment in 1970. You can retrieve this number of milliseconds by calling a static method on the Date class: Date.now():
Date.now();
// `1613787272648`
  • Date.now() can be useful for performing simple math for things like tracking how long some task took:
const start = Date.now();
await fetch(`/api.cats.com/breeds`);
const finish = Date.now();
const elapsed = finish - start;
console.log(`Network request took ${elapsed}ms!`);
  • But perhaps even more common than using Date.now() is instantiating a Date instance with new Date(). Creating a Date object/instance in this way allows you to work with a point in time as an object, and access various information about it through a slew of instance methods it provides:
const now = new Date();
now.getDate(); // `18` - day of month (1-31)
now.getDay(); // `3` - day of week (0-6)
now.getFullYear(); // `2021`
now.getHours(); // `14` - hour of day (0-23)
now.getMonth(); // `1` - month of year (0-11) ¯\_(ツ)_/¯
// etc... getMinutes(), .getSeconds(), .getTime(),
// etc... getTimezoneOffset(), and more...
  • One useful date method is .toLocaleString() which (theoretically) produces a nice human-readable version of a date/time that is appropriate for your language (locale):
new Date().toLocaleString(); // "2/18/2021, 3:53:17 PM"
  • Another handy method is .toISOString() which returns a string geared for computers to understand. A common use case for this method is to return a string **that is suitable for storing as a string in a database so that it can be turned back into a valid Date object again after getting pulled out of the database:
await saveBlogPostToDatabase({
  id: `123`,
  title: `All about goats`,
  author: `Kiah Henderson`,
  time: new Date().toISOString(), // <-- 👋
});

// then, many moons later... ⏳
const goatPost = await fetchPostFromDatabase(`123`);

// here we create a new date object referencing the
// moment in time when we saved the blog post long ago ^^^
const publishedDate = new Date(goatPost.time); // <-- 😎
  • the example above shows that the Date constructor accepts an (optional) argument. If you just call new Date() it gives you a date representing that instant. But it also accepts a dizzying variety of inputs, only a few of which are shown here:
let today = new Date(); // now
let birthday = new Date("February 23, 1979 03:24:00");
let birthday = new Date("1979-02-23T03:24:00");
let birthday = new Date(1979, 1, 23); // the month is 0-indexed
let birthday = new Date(1979, 1, 23, 3, 24, 0);
let birthday = new Date(628021800000); // passing epoch timestamp
  • the Javascript Date object is considered by basically everyone to be fundamentally flawed. That’s probably because Brendan Eich created the language in 10 days, and copied a flawed Date API from Java that was soon after scuttled and replaced. But because we can’t break the web we’re stuck with Date. For that reason, instead of fixing it, TC39 is working on a new API called Temporal which in a few years will likely completely replace the usage of the Date object.

Javascript: Destructuring w/ Defaults

  • we already know that we can destructure arrays and objects in javascript:
// destructure an OBJECT
const person = {
  name: "Jared",
  hasBeard: true,
};
const { name, hasBeard } = person;

// destructure an ARRAY
const coordinates = [121, 423];
const [x, y] = coordinates;
  • but sometimes our objects/arrays have OPTIONAL values. In that case, we can fill in missing values while destructuring, like so:
type Book = {
  title: string;
  numPages: number;
  isModernized?: boolean; // <-- note optional property
};

const turford = {
  title: "Walk in the Spirit",
  numPages: 121,
  // 👋 note: no `isModernized` property
};

// ✅ here we supply a DEFAULT VALUE while destructuring:
const { title, numPages, isModernized = true } = turford;
  • You can supply defaults while destructuring arrays as well:
// in this type, BOTH values might be undefined
type Coordinates = [number?, number?];

const myCoord: Coordinates = [10]; // 👋 note missing second

// ✅ here we supply a DEFAULT VALUE while destructuring:
const [x = 0, y = 0] = myCoord;
console.log(x, y); // > 10, 0

Javascript: Nullish Coalescing Operator ??

  • In year 2, week 3 we covered the || “logical OR operator”, which returns the left hand side if the left hand side is TRUTHY, otherwise the right hand side:
// ✅ these are intuitive
"foo" || "bar"; // "foo"
33333 || false; // 33333
false || 555; // 555

// 🚨 but these not so much...
"" || "hello"; // "hello" 🧐
0 || 25; // 0 🧐
  • if you examine the last two examples, you can see how this can cause bugs, because you have to remember special cases of boolean coercion, like “empty string is FALSY” and “number 0 is FALSY”.
  • using the || operator in the function below causes a bug, can you spot it?
function orderCoffee(cups?: number) {
  console.log(`You ordered ${cups || 1} cup/s of coffee`);
}
  • the “bug” in the above function is that if you try to order 0 cups of coffee, you get 1 cup:
orderCoffee(0);
// > "You ordered 1 cup/s of coffee" 🚨
  • it’s for cases like this, (often when you’re trying to supply a DEFAULT value), that javascript introduced an operator common in other languages called the nullish coalescing operator: ??.
  • the idea behind ?? is that it works just like || EXCEPT it returns the left hand side only if it is null or undefined:
"foo" ?? "bar"; // "foo"
true ?? null; // true
0 ?? 1; // 0 ✅
"" ?? "hello"; // "" ✅
  • the last two examples show how you can fix the bug in the orderCoffee function above.
  • it’s called “nullish” because javascript is weird in that it has two null-like values: null and undefined, and “nullish” sounds better than “undefinedish”. Other languages with only one null value call it “null coalescing” or “nil coalescing”.

React: Default Props

  • When we write regular functions in javascript, it’s sometimes useful to supply default values to arguments, to make a more ergonomic, convenient API for the caller of our function, so they don’t have to pass so many arguments, unless they want to. Like this function, for example:
function greet(name = "friend"): void {
  console.log(`Hello, ${name}`);
}

greet(); // `"Hello, friend"`
greet("Suzy"); // `"Hello, Suzy"`
  • the same thing is true for React components. Sometimes you want to make a component customizeable, but you know that most of the time it gets used the customization will not be used, so you don’t want to force a prop to always get passed. The way to handle this (in Typescript/React, at least), is by making an optional prop:
type Props = {
  name?: string; // <-- note the `?` 👋
};

const Greet: React.FC<Props> = ({ name }) => {
  return <h1>Hello, {name ?? "friend"}</h1>;
};

// then we can do both...
<Greet />
<Greet name="Suzy" />
  • take careful not that on line 6 above, the type of name was string | undefined because of the ? in the type. Because of that, I used the nullish coalescing operator to use either the string value of the name, or fall back to "friend".
  • but there is perhaps a better way to handle the optional name prop. Instead, we can supply a default value WHILE DESTRUCTURING the props, to guarantee that we have a type string within the component function body, like so:
type Props = {
  name?: string;
};

// 😎 supply a default value during destructuring
const Greet: React.FC<Props> = ({ name = "friend" }) => {
  return <h1>Hello, {name}</h1>;
};

Homework Plan

  • 1 days review all flashcards (in your app)
  • 1 day 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


    • make sure to slowly and carefully read and review the “Javascript Dates” section of “New Stuff” above ^^^.
    • make sure you’ve addressed all of my feedback from last assignment, then merge your MR.
    • connect with vscode, switch to master, pull from origin, delete your old branch and start a new branch
    • first we’re going to start by adding some client side validation to your “add a card” form. We don’t want users to be able to submit new cards with empty strings, which is probably possible right now. Let’s make it so that the form prevents some types of submissions for happening. Work on your form until these requirements are met:
      • if a user tries to submit the form with any of the fields empty, the form should not submit
      • the “submit” or “add card” button should be greyed-out or disabled (both visually and functionally) somehow if any of the form elements that are required are empty. As soon as valid input is detected, it should become “enabled” visually and functionally.
      • a few spaces should not count as valid input, so for instance, if for the name of the card I put 3 spaces, that should be still considered invalid, and the form should not submit.
      • also, extra leading and trailing whitespace should be removed from the input, so if I try to submit for the front of the card the string " pwd " it should actually send and store just the string "pwd".
      • also, for your card “category”, if you’re not using a select dropdown which guarantees that no new category will be created, then you need to either a) chang your card category input to be a safer dropdown, or b) add an extra layer of validation on the client that no unknown category can be supplied. A is much easier than B. :)
    • commit your work
    • next, move over to your server, and let’s add some similar validation there too. It’s tempting to think that since we added validation on the client (browser) side, that we don’t also need it on the server, but that’s not generally true. For instance, there might be a bug in our javascript that lets bad data through, or a hacker might try to craft an HTTP request and send it directly to our API, thus bypassing our client-side validation. Therefore…
    • add some validation on the server that meets these requirements:
      • if a card is submitted with an empty (or only whitespace) card front or back, do not save the card. Instead send back a 400 Bad Request response.
      • Extra Credit: ✨ check the category that is submitted against current known categories, if the category is not already present in the card data, reject the request as well, also with a 400.
    • commit your work
    • for our final feature of the week, we’re going to add “created” times to our newly added cards, created through the API. Here are some requirements for this feature:
      • for each new card added, the server should add the time it was created in a "created" property of the card. You’ll need to use a Date object, and I would recommend storing the date by making the .created property be the string returned by dateObj.toISOSTring(), as shown in the “new stuff” above ^^^.
      • then, on the client, you’re going to want to display these dates somehow, when reviewing cards. BUT, only some of your cards will have the date, — only the cards you add after this feature is complete. So we’ll need to modify your <Card /> component so that it optionally takes a date, and the card needs to look good with and without a date. This is a perfect time to use Storybook. So…
      • fire up your Storybook server, and add a new story for your “Card” component, which will be for the purpose of designing how you want the Card to look when there IS a date. And, of course, you’ll need at least one story for a card with no date (which should already be covered by an existing story).
      • modify the props that your “Card” component takes, to indicate that it also takes a date, but that the date might not always be there. Make sure to express this correctly with a good Typescript Type.
      • you can decide what the type of the prop will be, you might want to pass it the ISO string you get from the server, or you might want to turn that string back into a Date object before it gets passed down as a prop, in which case the type can be Date, either way is fine. (Remember, classes are both values AND types in Typescript, so Date is a perfectly valid type as well.)
      • for your new story where you pass your Card component a date, figure out where and how you’d like to display your date. You can use any combination of methods on the Javascript Date object, or look here for more documentation on formatting Dates. You might want to just show the day that the card was created, or maybe the time as well. You might want to prefix it with a word like Added: 2/23/21, or anything else that seems good to you, you decide.
      • make sure that your card looks good (and does not Error) when you have no card — you should have a storybook story covering this case as well.
      • once you’ve got it rendering nicely in Storybook, wire things up in your web-app by passing the created date to the card prop. Test that things are working by adding a new card, you should see the date it was added when that card comes up for review, while all of the old cards should show no date.
    • commit your work
    • build your site, build storybook, submit a MR, slack all 3 of the URLs

    Akron Snowmen Website


    • Make sure you’ve slowly and carefully read the “Destructuring with defaults” and “Default prop values” section of “New Stuff” above ^^^.
    • Connect to your Akron Snowmen repo, switch over to master, pull from upstream delete your branch from last week.
    • Create a new branch.
    • Fire up Storybook, because you’re going to make 2 new components.
    • the First component I want you to make is a “Hero” block for a new “Team” page we’re going to build. “Hero” is just a fancy web-developer word for the most important, biggest block at the top of the screen. We already have a “Hero” for our homepage. The idea for this hero is to have a big, cool-looking main block for “Team” page as well.
    • peek in the public/ directory, you’ll see that I added 3 images there that Rachel took last week.
    • starting with a mobile screensize, design a Hero block that uses at least one of the new images, and has some text about the Akron Snowmen team. You can refer to the copywriting document posted a couple weeks ago to get some text for this block. You’ll probably want to use the big group picture somehow, but you can get creative if you have a different idea. You might want to have the image be the entire background of the block (like the homepage hero), or maybe not. You can decide. Don’t go crazy, but spend a little bit of time trying to make it look presentable. Imagine you’re going to have to show it to Rod, Kristi, and Josue (because soon you will). You also might want to make it 100vh to match the homepage block — again the decision is yours.
    • make sure you block also looks good at larger screen sizes. Resize your browser window a bunch to test that there are no funky in-between sizes where it looks janky.
    • Commit your work.
    • Next we’re going to design work on displaying each team member in their own block. Ideally, what we’d like is to show:
      • their name
      • their picture
      • a short bio
    • If you think about it, we’ve already got a component that is pretty well suited for this, the ImageAndTextBlock component. Let’s see if we can stay DRY and re-use this component, by slightly modifying it to work for team members.
    • Open the ImageAndTextBlock.stories.tsx file. Let’s export another named story (besides BasicStory) to cover the use-case we’re working on. Name the named export TeamMember, like so:
    export const TeamMember = () => {
      // ...tsx here
    };
    • Pass the component one of the images of Josue from the images directory, and a his name as the headerText, with a fake bio as the children. Pick some background color that seems good to you (you can just copy one of the props from the other story).
    • It should look pretty good as is, except, that it has a “Learn More” button. That makes sense for how this component is used on the homepage, but not in this context.
    • Modify the component so that it takes a prop indicating whether or not it should have a “Learn More” button — but here’s the kicker: make the prop have a default value so that you don’t have to pass the prop. We shouldn’t have to change the places where the component is already used. Rather, when we want to “turn off” the button, we can pass a prop to override the default value.
    • Finally, to mix things up a little bit, it would be nice if we could alternate the alignment of the images, to create a “staggered” appearance, so that when we use this component to show Rod, Kristi, and Josue’s blocks, we can have Rod’s image on the left, then Kristi’s image on the right, and then Josue’s image on the left again.
    • Add another optional prop (with a default value) that allows you to specify the alignment. Give it a really nice, precise Typescript type, and make sure to give it a default value (you shouldn’t have to modify how the component is used in pages/index.tsx).
    • before you try to make this feature work, add another Storybook story to make sure you’ve got one for left alignment and right alignment, have the right aligned story visible while you’re implementing the feature below. (Hint: If I were doing this, I might make a composite story that shows two blocks within the same story (like BasicStory shows 3), one for each alignment. That way, I could work on the right-alignment and immediately see in Storybook if I broke the left alignment)
    • With your storybook story open to guide you, use your React/Html/CSS wizardry to make the feature actually work, aligning the image either on the left or the right based on the prop. (Note: for very small screens, the image should always still be on top)
    • Hint: don’t be tempted to rely on the order the elements appear in the HTML here, that will probably foul up the small screen requirement I mentioned above (see Note: in last step). Instead, use your CSS skills to modify the order the elements are displayed in, without changing the HTML markup to put the image before or after the text portion.
    • Once you’ve got it looking right in Storybook, kill your storybook server, and commit your work.
    • next, fire up regular dev server, then use your new work to quickly flesh ou t the pages/our-team.tsx file. In it, put the new Team Hero component, and use the ImageAndTextBlock components three times, once for Rod, Kristi, and Josue (make sure to alter the alignment)
    • commit your work.
    • build your site, submit a MR, and post both URLs in slack.

    Personal Project Assignment


    • NOTE: Some of you got started on your personal projects already, which is just fine (though not required). If you did, skip to step STEP 13, you’ll do a slightly different assignment than those just starting out.
    • If you haven’t started your personal project yet slack me for some info on how to get started on the project (so you don’t have to create all the boilerplate for npm, react, storybook, parcel, etc)
    • Once you’ve cloned down the boilerplate repo I recommended, you’ll need to start this over as a brand new git repo, since you don’t want to be committing on top of the boilerplate repo I gave you. See if you can remember how to convert an existing git-repo into a brand new repo, and do that. If you can’t figure it out, check some old “New Stuff”, and then Slack me, if you’re still stumped.
    • Before you do any work, but after you created a brand new repo, do an initial commit, adding all the files from the repo.
    • Next, create a new repo on Gitlab for this project.
    • Add the new Gitlab repo as a remote called “origin”. (You have to do this manually because blew away the old, wrong origin remote of the boilerplate repo in the step above). If you can’t remember, search the homework site for “Git remote” and find the correct info.
    • Push up master to your new repo on Gitlab (you still should not have done any work. The reason we’re pushing up master with just the files from the boilerplate is so that you can do merge requests, so that I can see the code you’ve added.)
    • Next, create a new branch
    • Check the package.json file and make a few tweaks to it so it doesn’t reference the boilerplate. Especially be sure to modify the “build” script (if it has one) to build into a folder that is properly named, not something like www/parcel-boilerplate.
    • Do the first chunk of work you planned for yourself in your project planning homework from last class.
    • when you’re done, make sure you’ve committed all your work, build your site, and push up a MR.
    • 🛑 STOP at this point (the rest of the instructions are for those who already started)
    • 👋 **This step and beyond are only for those that ALREADY started. Before you do any more work, commit what you’ve got, and then spend a few minutes going over your code to:
      • remove console.log()
      • run prettier
      • rename poorly named variables/components
      • remove any files or chunks of code you’re not using
      • generally making the code ready for review
    • once you’ve done the above, commit your work, and slack me during “office hours”, sending me the directory of the project as it currently stands. I’ll poke around for a minute and we’ll get some stuff sorted out so that I can review your work in a MR. Follow whatever directions I give you.
    • while I’m slacking with you, I’ll either get you to the point where you submit a MR, or explain how you can do it on your own. Either way, submit the MR, build your site, and slack me both URLs.
    • I’ll review your MR, and communicate 2 things:
      • one, I’ll leave feedback on some things I’d like you to change/improve
      • two, depending on how many changes I request, I’ll either tell you that addressing my feedback will be enough for this week’s assignment, or I’ll tell you to do another chunk of work after you fix the feedback.
    • Address the feedback on the MR I gave you, then, merge it.
    • ONLY IF (according to step 16b) I said that you should go on to do another chunk of work, then:
      • switch over to master, pull your merged in changes (from step 17)
      • create a new branch
      • do the first chunk of work you planned for yourself in last weeks project planning assignment
      • commit your work, build, and submit another MR
      • slack me both URLs (again)
    ← All homework