Skip to content

Commit 1ed8fac

Browse files
committed
Initial commit
0 parents  commit 1ed8fac

File tree

97 files changed

+18793
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+18793
-0
lines changed

Diff for: .gitignore

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
21+
# debug
22+
npm-debug.log*
23+
yarn-debug.log*
24+
yarn-error.log*
25+
26+
# local env files
27+
.env.local
28+
.env.development.local
29+
.env.test.local
30+
.env.production.local

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a starter template for [Learn Next.js](https://nextjs.org/learn).

Diff for: bin/generate

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
3+
rm content/*
4+
obsidian-export /Users/stevekinney/Library/Mobile\ Documents/iCloud\~md\~obsidian/Documents/React\ and\ TypeScript content

Diff for: components/content-layout.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { PropsWithChildren } from 'react';
2+
3+
const ContentLayout = ({ children }: PropsWithChildren) => {
4+
return (
5+
<section className="p-8">
6+
<div className="items-start gap-8 lg:flex lg:flex-row lg:place-content-around">
7+
<div className="w-full mx-auto prose max-w-prose">{children}</div>
8+
</div>
9+
</section>
10+
);
11+
};
12+
13+
export default ContentLayout;

Diff for: components/footer.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import clsx from "clsx";
2+
import Link from "next/link";
3+
4+
const FooterLink = ({
5+
href,
6+
site,
7+
username,
8+
}: WithChildren & { href: string; site: string; username: string }) => (
9+
<Link href={href}>
10+
<a className="underline underline-offset-2">
11+
<span className="hidden sm:inline">
12+
<strong>{username}</strong> on{" "}
13+
</span>
14+
<span className="font-semibold sm:font-normal">{site}</span>
15+
</a>
16+
</Link>
17+
);
18+
19+
const Footer = ({ className }: WithClassName) => (
20+
<footer
21+
className={clsx(
22+
"sticky flex place-content-center gap-4 bg-slate-900 p-4 text-white",
23+
className
24+
)}
25+
>
26+
<FooterLink
27+
href="https://twitter.com/stevekinney"
28+
username="@stevekinney"
29+
site="Twitter"
30+
/>
31+
<FooterLink
32+
href="https://github.com/stevekinney"
33+
username="@stevekinney"
34+
site="Github"
35+
/>
36+
<FooterLink
37+
href="https://frontendmasters.com/teachers/steve-kinney/"
38+
username="Steve Kinney"
39+
site="Frontend Masters"
40+
/>
41+
</footer>
42+
);
43+
44+
export default Footer;

Diff for: components/header.tsx

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import clsx from 'clsx';
2+
import WithLink from './with-link';
3+
4+
const Header = ({ className }: WithClassName) => (
5+
<header
6+
className={clsx(
7+
'bg-primary-50 p-4 text-center md:p-8 md:text-left',
8+
className,
9+
)}
10+
>
11+
<WithLink href="/">
12+
<div className="flex items-center gap-4">
13+
<h1 className="text-3xl font-light transition-all border-b-4 w-fit border-primary-500 hover:border-primary-300">
14+
React && TypeScript
15+
</h1>
16+
<p>Version Two</p>
17+
</div>
18+
</WithLink>
19+
</header>
20+
);
21+
22+
export default Header;

Diff for: components/layout.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Footer from "./footer";
2+
import Header from "./header";
3+
4+
const Layout = ({ children }: WithChildren) => {
5+
return (
6+
<main className="w-full">
7+
<Header className="drop-shadow-md" />
8+
{children}
9+
<Footer className="bottom-0 w-full" />
10+
</main>
11+
);
12+
};
13+
14+
export default Layout;

Diff for: components/markdown.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import clsx from 'clsx';
2+
import ReactMarkdown from 'react-markdown';
3+
import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
4+
5+
const codeBlockLanguage = /language-(\w+)/;
6+
7+
const getCodeBlockLanguage = (className?: string) => {
8+
const match = codeBlockLanguage.exec(clsx(className));
9+
return match ? match[1] : null;
10+
};
11+
12+
const components = {};
13+
14+
const Markdown = ({ children, ...options }: ReactMarkdownOptions) => {
15+
return (
16+
<div className="content-index">
17+
<ReactMarkdown
18+
{...options}
19+
components={{ ...components, ...options.components }}
20+
>
21+
{children}
22+
</ReactMarkdown>
23+
</div>
24+
);
25+
};
26+
27+
export default Markdown;

Diff for: components/with-link.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Link from "next/link";
2+
3+
const WithLink = ({
4+
children,
5+
href,
6+
className,
7+
}: WithChildren & WithClassName & WithHref) => {
8+
if (!href) return <>{children}</>;
9+
return (
10+
<Link href={href}>
11+
<a href={href} className={className}>
12+
{children}
13+
</a>
14+
</Link>
15+
);
16+
};
17+
18+
export default WithLink;

Diff for: content/Adding state to Accident Counter.md

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
Let's quickly add some state to a simple little counter. For starters, this is basically the entire implementation.
2+
3+
````tsx
4+
const Counter = () => {
5+
return (
6+
<section className="flex flex-col items-center w-2/3 gap-8 p-8 bg-white border-4 shadow-lg border-primary-500">
7+
<h1>Days Since the Last Accident</h1>
8+
<p className="text-6xl">0</p>
9+
<div className="flex gap-2">
10+
<button>➖ Decrement</button>
11+
<button>🔁 Reset</button>
12+
<button>➕ Increment</button>
13+
</div>
14+
<div>
15+
<form onSubmit={(e) => e.preventDefault()}>
16+
<input type="number" value={0} />
17+
<button type="submit">Update Counter</button>
18+
</form>
19+
</div>
20+
</section>
21+
);
22+
};
23+
24+
export default Counter;
25+
````
26+
27+
This is nothing particularly special to see here. Adding some state can be done swiftly using the `useState` hook.
28+
29+
````tsx
30+
const [count, setCount] = useState(0);
31+
````
32+
33+
And then—yea—we'll add some state to our counter, just like every other modern React tutorial you've ever seen.
34+
35+
````diff
36+
@@ -1,12 +1,16 @@
37+
+import { useState } from 'react';
38+
+
39+
const Counter = () => {
40+
+ const [count, setCount] = useState(0);
41+
+
42+
return (
43+
<section className="flex flex-col items-center w-2/3 gap-8 p-8 bg-white border-4 shadow-lg border-primary-500">
44+
<h1>Days Since the Last Accident</h1>
45+
- <p className="text-6xl">0</p>
46+
+ <p className="text-6xl">{count}</p>
47+
<div className="flex gap-2">
48+
- <button>➖ Decrement</button>
49+
- <button>🔁 Reset</button>
50+
- <button>➕ Increment</button>
51+
+ <button onClick={() => setCount(count - 1)}>➖ Decrement</button>
52+
+ <button onClick={() => setCount(0)}>🔁 Reset</button>
53+
+ <button onClick={() => setCount(count + 1)}>➕ Increment</button>
54+
</div>
55+
<div>
56+
<form onSubmit={(e) => e.preventDefault()}>
57+
````
58+
59+
We'll worry about the form in a hot minute.
60+
61+
## Taking a look at what TypeScript has figured out for us
62+
63+
So, this is where it gets kind of cool. This file doesn't look like TypeScript, but it *is*.
64+
65+
If you hover over `count`, you'll see that TypeScript was about to deduce that it's a number because you set its initial value to a number.
66+
67+
````ts
68+
const count: number;
69+
````
70+
71+
You can also see that it figured out that `setCount` should *only* take a number, which means that `count` will *always* be a number.
72+
73+
````ts
74+
const setCount: React.Dispatch<React.SetStateAction<number>>;
75+
````
76+
77+
Don't worry about all of that ceremony. `useState` is an abstraction over `useReducer`, which works by dispatching actions.
78+
79+
If we try to break the rules, you'll see that TypeScript keeps us honest.
80+
81+
![](_attachments/Pasted%20image%2020221107062434.png)
82+
83+
This is *particulary* helpful when dealing with one of my least favorite parts of the browser: the fact that number inputs store their values as strings. 🙄
84+
85+
Let's take this head on and try to [get that little form for manually setting the count working](useState,%20an%20exercise.md).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remember in that filtering exercise that I was complaining about `<input type="number" />` components and how they give me strings? Using `Number()`, the `+` unary operator, and `parseInt()` all had different drawbacks, but newer versions of TypeScript allow us greater flexibility with template literals. We can now refactor that code as follows:
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
* `React.PropsWithChildren`
2+
* `React.ComponentPropsWithoutRef`
3+
* `FunctionalComponent<Props>` and React 18
4+
* `React.HTMLProps`
5+
6+
Let's say you want to extend props for a given React component to take children. You can use `React.PropsWithChildren`.
7+
8+
This might look something like this:
9+
10+
````tsx
11+
type ButtonProps = React.PropsWithChildren<{
12+
onClick: () => void;
13+
}>;
14+
15+
const Button = ({ children, onClick }: ButtonProps) => {
16+
return <button onClick={onClick}>{children}</button>;
17+
};
18+
````
19+
20+
You also have `React.ComponentPropsWithoutRef`, which you could use as follows:
21+
22+
````tsx
23+
type ButtonProps = React.ComponentPropsWithoutRef<'button'>;
24+
25+
const Button = ({ children, onClick, type }: ButtonProps) => {
26+
return (
27+
<button onClick={onClick} type={type}>
28+
{children}
29+
</button>
30+
);
31+
};
32+
````
33+
34+
Now, `Button` has all of the some props as the native `<button>` element from the DOM.
35+
36+
(**Note**: This no longer works in React 18.)
37+
38+
You can also use a [`FunctionalComponent<Props>`](https://www.newline.co/@bespoyasov/how-to-define-props-with-children-in-react-typescript-app--56bd18be#using-functioncomponent-or-fc) to accomplish a similar goal:
39+
40+
````tsx
41+
type ComponentProps = {
42+
foo: string;
43+
};
44+
45+
const Component: React.FunctionComponent<ComponentProps> = ({ foo }) => (
46+
<span>{foo}</span>
47+
);
48+
````
49+
50+
You can also extend a built-in HTML element, which supports children:
51+
52+
````tsx
53+
export interface Props extends React.HTMLProps<HTMLDivElement> {
54+
heading: string;
55+
}
56+
````
57+
58+
Now, you can do something like this:
59+
60+
````tsx
61+
import React, { HTMLAttributes, PropsWithChildren } from 'react';
62+
63+
interface ComponentProps extends HTMLAttributes<HTMLDivElement> {
64+
name: string;
65+
}
66+
67+
const Component: React.FC<PropsWithChildren<ComponentProps>> = ({
68+
name,
69+
children,
70+
...rest
71+
}) => {
72+
return (
73+
<div>
74+
<div {...rest}>{`Hello, ${name}!`}</div>
75+
{children}
76+
</div>
77+
);
78+
};
79+
````

0 commit comments

Comments
 (0)