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 withnew Date()
. Creating aDate
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 callnew 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 withDate
. For that reason, instead of fixing it, TC39 is working on a new API calledTemporal
which in a few years will likely completely replace the usage of theDate
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 get1
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 isnull
orundefined
:
"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
andundefined
, and ânullishâ sounds better than âundefinedishâ. Other languages with only onenull
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
wasstring | 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 typestring
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.
- if a card is submitted with an empty (or only whitespace) card front or
back, do not save the card. Instead send back a
- 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 aDate
object, and I would recommend storing the date by making the.created
property be the string returned bydateObj.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 beDate
, either way is fine. (Remember, classes are both values AND types in Typescript, soDate
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.
- for each new card added, the server should add the time it was created
in a
- 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 (besidesBasicStory
) to cover the use-case weâre working on. Name the named exportTeamMember
, 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 thechildren
. 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 theImageAndTextBlock
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 likewww/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
- remove
- 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)