Year #2, Week #15 💻 🚂
New stuff we learned this week: 🤔
Try/Catch/Throw
- Javascript has a built in
Error
object, which you can create instances of using thenew
keyword, like so:
const myError = new Error(`uh oh!`);
Error
object instances have a.name
and a.message
property. The.name
is not usually helpful, it’s always just the string"Error"
unless you manually override it. But the.message
property is useful, giving the string that was passed to the constructor:
const myError = new Error(`Unexpected goat`);
myError.name; // > "Error" - not super useful
myError.message: // > "Unexpected goat" - more useful 👍
- it’s not that often that you capture an error object into a variable like shown above. Much more commonly you THROW an error:
throw new Error(`oh noes!`);
- to understand throwing errors, it’s helpful to remember that javascript sometimes crashes because of errors, stuff like, if you try to do something illegal or impossible in your code, when the javascript engine reaches that code, it just crashes and breaks and stops execution, like in this example (which is only possible because we’re not using typescript):
let myString; // whoops, forgot to initialize it
// 🚨 RUNTIME Error!!!
// Uncaught TypeError: Cannot read property 'toUpperCase' of undefined
myString = myString.toUpperCase();
// we'll NEVER get here - line 5 causes a runtime error
console.log(myString);
- so errors crash and terminate your program. Wouldn’t it be cool if you could crash and terminate your program from within code? You can, by throwing an Error.
function printAgeGreeting(age: number): void {
if (age > 40) {
console.log(`Howdy old person`);
} else if (age > 0) {
console.log(`Hello spring chicken`);
} else {
// 🚨 will crash your program with an error!
throw new Error(`Unexpected negative age!!!`);
}
}
- but why would you ever want to crash your own program by throwing an
error?
- For one, you would prefer your program to crash if you get truly unexpected data of some sort. In that case it’s often better to crash the program than to keep dealing with the weird data, and letting it percolate into other parts of your system, or maybe get stored in your database.
- the other reason you might want to throw an error is that you’re planning to CATCH it somewhere else in your code.
- How do you catch an error? If you have some code you want to run that
might throw, you can wrap it in a
try/catch
block:
try {
thisFunctionMightThrow();
} catch (err) {
console.warn(`Oops, an error occurred: ${err.message}`);
}
- you can have some code throw an error DEEP in your program, nested many function calls down. If it’s not immediately caught, the error will keep propogating upward. So, in theory, you can catch every error in an application if you wrap just the outermost spot that kicks off the javascript with a try/catch.
- if you never catch an error, it makes it all the way to the node or browser runtime, and crashes your program.
TypeScript: Complex/Composite Types
- we’re already very comfortable annotating variables and functions with primitive types in TypeScript:
let num: number = 5;
let bool: boolean = true;
let name: string = "jared";
let empty: undefined = undefined;
let soNull: null = null;
- And we know how to type arrays of primitives:
let name: Array<string> = ["jared", "willow", "tabitha"];
let bools: Array<boolean> = [true, false, true, true];
let mixed: Array<boolean | number> [1, 4, true, 6];
// or if an array is of an explicit LENGTH
// we can make it a TUPLE
let tuple1: [boolean, string] = [false, "howdy"];
let tuple2: [number, number, string] = [3, 3, "three"];
- in a sense arrays are a simple composite type — a type make of other types, or a list of values each of which can have it’s type described in TS.
- we’ve also briefly looked at how to type objects. Objects can be thought of as a composite type as well, it’s where each key is a string and each value is some primitive type:
type Car = {
make: string;
model: string;
mileage: number;
convertable: boolean;
};
- and object/composite types can be infinitely nested creating the ability to make arbitrarily complex (or specific) types:
type Person = {
name: string;
age: number;
primaryVehicle: Car; // <-- look 😎
vehicles: Array<Car>; // <-- here too
};
- the above works great for very specifically defined objects with known keys, but what if you want an object that might have lots of different keys at runtime? For that case, you use an index signature:
type Definitions = {
[word: string]: string;
};
const myDict: Definitions = {};
// ✅ works
myDict.goat = "barnyard animal";
myDict.helicopter = "transportation device";
- the
word
part on the left hand side above is just a helpful hint for humans, it’s not used anywhere by Typescript to compile or typecheck. We could have made itkey
ork
orhamSandwich
. - making richly expressive composite types is an incredibly useful tool for managing complexity in apps, especially when you design your types before writing your code.
TypeScript: Generics
- a “Generic” in typescript is a type with some kind of a “hole” in it — meaning a type that contains some sort of information about the STRUCTURE of a composite type, but is flexible to permit different INNER types.
- the simplest generic we’ve already seen a lot is the array type:
Array<T>
whereT
is any type:
type Strings = Array<string>;
type Booleans = Array<boolean>;
type Mixed = Array<string | number | boolean>;
type Arrays = Array<Array<any>>;
- generic types always use angle brackets to hold one (or more) types:
<T>
, or<T, K>
, these are called type arguments, because if you squint at them they’re sort of like arguments to a function, except they are arguments to a type. - we can make our own generic types, by using a stand-in type name (usually
T
for “type”), like so:
type Pants<T> = {
leftPocket: T;
rightPocket: T;
};
- the above declaration declares a type of an object with a
leftPocket
and arightPocket
property, both of which are of typeT
. What’s typeT
? Exactly!! That’s for the user to decide. The type is generic — it’s flexible to adapt to different types:
const numberPants: Pants<number> = {
leftPocket: 4,
rightPocket: 5,
};
const booleanPants: Pants<boolean> {
leftPocket: true,
rightPocket: false,
};
const arrayPants: Pants<Array<string>> {
leftPocket: ["goat", "banjo"],
rightPocket: ["herp", "derp"],
};
- because
Pants<T>
is a valid type, it can even be passed in as it’s ownT
, if you love recursive pants:
// Recursive Pants are Recursive 👖 👖 👖 👖 👖 👖
const recursivePants: Pants<Pants<number>> = {
leftPocket: { leftPocket: 1, rightPocket: 2 },
rightPocket: { leftPocket: 1, rightPocket: 2 },
};
- generic types can have more than one type, just separate them by commas:
type MixedPants<T, K> = {
leftPocket: T;
rightPocket: K;
};
const myMixedPants: MixedPants<string, null> = {
leftPocket: "Jared",
rightPocket: null,
};
- generic types can also supply default values:
// 😎 if `T` is not supplied, it will be `string`
type EasyPants<T = string> = {
leftPocket: T;
rightPocket: T;
};
// 😎 look ma, no angle brackets!
const easy: EasyPants = {
leftPocket: "hamburger",
rightPocket: "goat",
};
- generic types don’t have to be objects, you can plug them in to any spot where you want a configurable “hole” in your type:
// v-- type arg
type GenericTuple<T> = [string, number, T];
let myTuple: GenericTuple<boolean> = ["foo", 3, false];
- other things can have generic types in TS too, like classes, and functions. Generic functions are somewhat common, and are quite powerful. They work just like generic types, except you put the type argument before the parentheses, like this:
// this function wraps things in an array
function arrayIfy<T>(input: T): Array<T> {
return [input];
}
arrayIfy("goat");
// > ["goat"]
arrayIfy(false);
// > [false]
arrayIfy([undefined]);
// > [[undefined]]
- in all the above cases, TS correctly infers the type you passed in. However, sometimes TS will “widen” the type it infers in a way you don’t want. In that case, you can specify the type of what you’re passing in at the call site, like this:
type Player = "red" | "black";
// 👎 TS gives us the type Array<string>
let attempt1 = arrayIfy(`red`);
// ✅ help TS out by specifiying the type:
let attempt2 = arrayIfy<Player>(`red`);
CSS: calc() Function 🧮
- the CSS calc function lets you perform calculations on the right-side of a css declaration, anywhere a number-like value is expected.
p {
/* 300px */
width: calc(100px * 3);
}
- the beauty of the
calc()
function is that you can mix-and-match units to calculate stuff that can only be determined when the browser is rendering:
p {
/* 😎 */
width: calc(100vh - 5rem + 30px);
}
- it’s OK to use
calc()
inside of css variable declarations, AND to use css variables inside ofcalc()
:
root: {
--inner-width: calc(35% + 150px);
--main-width: calc(100vw - (var(--inner-width) / 2));
}
CSS: box-sizing
- the CSS
box-sizing
property controls how the total width and height of an element is calculated. - the default value for
box-sizing
iscontent-box
which means that the width and the height of an element DO NOT INCLUDE border or padding. So, for example, take this CSS:
/* width of <p> will end up being 524px 🤔 */
p {
width: 500px;
padding: 10px;
border: 2px solid hotpink;
}
- calculating the width or height of elements and remembering to add in padding
and border is complicated and easy to mess up, and gets almost impossible if
your padding is expressed in units like
em
orrem
. - because of this, CSS introduced
box-sizing: border-box;
which forces the width to be calculated to INCLUDE padding and border:
/* width of <p> will end up being 500px 😎 */
p {
box-sizing: border-box; /* <-- ✅ */
width: 500px;
padding: 10px;
border: 2px solid hotpink;
}
- look carefully at that above example — do you see how in one sense the padding and border dimensions sort of get subtracted from the total width? That’s important to realize.
- basically, everybody wishes that the default value was
border-box
but we can’t break the web, so the default is stillcontent-box
. For that reason, many frameworks and developers always start their CSS with a little snippet flipping the default around, which looks like this:
* {
box-sizing: border-box;
}
Homework Plan (two weeks)
- 2 days review all flashcards (in your app)
- 1 day Flashcard app refactor assignment
- 1 day Typescript Assignment
- 1 day Request/Response homework
- 1 day Akron Snowmen assignment
- 2 days touch typing practice
- 8 days Execute Program homework
Typescript Assignment
- slowly and carefully review BOTH typescript sections from the “New Stuff” above ^^^.
- create a FORK of this repo, then clone down your fork with vscode.
- create a new branch
- open the
types.ts
file, and slowly work through each problem, reading the comment-instructions and working on the types until you have the strictest possible type that makes the section typecheck correctly. - when you’re done, commit your work and open up a MR, slack the URL as usual.
Request/Response Homework
- if you haven’t done the typescript assignment, do that first
- create a FORK of this repo, then clone down your fork with vscode.
- create a new branch
- install the dependencies
- run the tests in watch mode with
npm run test:watch
or run them manually withnpm run test
. - start with the
src/create-request.spec.ts
file, working one test at a time, changingxit
toit
andxtest
totest
as needed. - as you work on getting the tests to pass, you should be crafting and improving
the
Request
TYPE in thesrc/request.ts
file as well. - you’ll need to probably use all of your tricks, splitting strings, maybe regex, for loops, anything goes.
- take babysteps and think carefully through each step of the process.
- when you have
create-request.spec.ts
tests all passing, commit your work and move on tosrc/req-string.spec.ts
. Those tests will guide you through creating a function that takes yourRequest
object and turns it back into a string. - work through each test, one by one, getting them to pass.
- hint: if you want to isolate just one test and maybe
console.log
something, change the test wrapping function toit.only(...)
ortest.only(...)
and jest will run just that test. - NO
any
orunknown
TS types allowed ANYWHERE (when you’re done). - when you’ve got them all passing, commit your work, create a MR, and slack the URL.
Akron Snowmen Homework
- connect to your AS repo in vscode, switch to
master
, pull fromupstream
and then you can force-delete your branch from last week. You can force-delete it because I’m not merging the student MR’s for that project. - create a new branch for this weeks work.
- find the description of work assigned to you below, and complete it. Then build, submit a MR, and slack both URLs.
- hint: a really good pattern for making components flexible and re-usable
in various contexts is to have them accept an optional
className
prop. That way, a component using it can pass a custom className prop if it needs to add special styles.
Akron Snowman Sub-Assignments
- Kiah:
- carefully study the sizing of the button on the example site, testing it at different screen sizes, from mobile, to large-screen
- do whatever work is necessary to get the width and appearance of the button working correctly in all of the components of our site
- you will likely have to extend the
Button
component to accept some kind of prop or props to allow the component that uses it to control what width or widths it should display at.
- Willow:
- add Icons to the Grid block. You can try to match the ones on the example site, or pick others.
- fix the color of the Pink block, and dial in the spacing and appearance of that whole block to more perfectly match the example site.
- be sure to check all screen-sizes, the block should render correctly at all screen sizes.
- Win:
- carefully study the “body” text on the example site — “body” text means non-header text, sort of the paragraph-like longer blurbs of text. They all have about the same appearance.
- create a
<BodyText>
component that encapsulates all of the styling necessary to make these chunks look exactly right. - create a storybook story for the component
- be sure the line-height looks right.
- check for anything needed for different screensizes
- integrate your new
<BodyText />
component everywhere it should be used in all of the other components.
- Harriet:
- Carefully compare the sizing of the image and text portions of the
<ImageAndTextBlock>
component on our site, and the example site. - spend some time dialing in the css so that the widths and heights are exactly correct, and match the example site at ALL screen sizes.
- Carefully compare the sizing of the image and text portions of the
- Tabitha:
- carefully study the Footer component on the example site, especially how it’s items are aligned and spaces at all screen-sizes, not just small-medium and large.
- especially take note of how the footer looks together with the nav bar appearing on the right.
- make any needed adjustments to the HTML/CSS of the footer to get it looking exactly correct in it’s new spot in the overall layout.
Flashcard App Refactor Assignment
- we’re going to be starting to make some big changes to the Flashcard app soon, so to prepare the way, I want you each to do a little cleanup and refactoring of your codebase. The cleaner and simpler and smaller your codebase is, the easier it is to change.
- before you start, you’ll need to merge in your most recent branch (probably
called
icons
) through GitLab. - once you’ve merged in GitLab, connect with vscode, swich to master, pull from
origin, delete your old branch, and start a new one called
refactor
. - for each of you, I pulled down your code and looked through it all, and made a short video pointing out what I’d like you to change.
- to help you, I’ve made a short list of the items I pointed out in your video so you can refer to the list, instead of having to re-watch the video over and over.
- once you’ve got your refactoring tasks done, submit an MR, build, and slack both URLs.
Flashcard App Refactor Sub-Assignments
- Tabitha:
- your video is here
- fix stories/ dir story files typescript errors
- simplify
Button.tsx
style logic by using class & .css file Button.tsx
- remove unused backgroundColor propCard.tsx
- use theh1WhenFront/Back
classes to set colorChrome.tsx
, line 51 and 61 — nestedif
statements
- Willow:
- your video is here
- (note: i had the wrong branch checked out in my video, so the code won’t be 100% the same, but all the ideas should be clear)
- fix typescript errors in
src/stories
dir - get rid of inline style logic in
Button.tsx
Chrome.tsx
(clean up doubleif
(line 45, 55))
- Win:
- your video is here
- stories dir fix import paths, get storybook working
Button.tsx
- movedisabledStyles
into .css file, thru setting a class insteadCard.tsx
- I would move all styling logic into class/css approachDeckChooser.tsx
- remove fragment
- Harriet:
- your video is here
- delete your
App.tsx
file if you’re not using it… - error from wrong props in
src/index.tsx
- get rid of unused useEffect import on
Kard.tsx
- fix card-jumping left/right based on not rendering first button (opacity 0 instead?)
- in
Khrome.tsx
- simplify nested if statements
- change
backgroundColor
prop to category component (should beactive
orselected
— some boolean prop)
- Kiah:
- your video is here
- fix build error from missing
src/palette.js
file - delete the my-react dir
- fix your Storybook (Chrome.tsx is erroring) so it runs and all stories render correctly
- in
App.tsx
:- oneC, twoC, threeC…— refer to video
mode
color mode should not be passed down, set a class for light/dark mode so you can remove a ton of inline styling logic scattered throughout
- in
Chrome.tsx
:- remove
styleIt
(unused fn) - all the dark-mode light-mode stuff should be triggered by a single class, and be tucked away in css.
- remove
- deckbar.js — seems to be unused, can it be deleted?
- dropdown.js — incorporate this as a TypeScript react component
- palette.js — incorporate this as a TypeScript react component
- DeckLink.tsx - refactor complicated
stylit
function to class + external css