Skip to content
This repository was archived by the owner on Sep 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"eslint": "eslint --ext .js,.ts,.tsx .",
"eslint:fix": "eslint --ext .js,.ts,.tsx --fix .",
"svgr": "svgr --ext tsx --no-svgo --no-dimensions -d .",
"snippets": "runSnippet snippets/bin/generateSnippetsBundle.ts",
"snippets": "yarn runSnippet snippets/bin/generateSnippetsBundle.ts",
"runSnippet": "ts-node --project tsconfig.snippets.json"
},
"devDependencies": {
Expand Down
12 changes: 12 additions & 0 deletions snippets/snippets/generics/bfka.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// We want to modify createState() to support
// creating two different states:

// One that only allows numbers, and…
const numState = createState()
numState.setState(1)
console.log(numState.getState()) // 1

// The other that only allows strings.
const strState = createState()
strState.setState('foo')
console.log(strState.getState()) // foo
13 changes: 13 additions & 0 deletions snippets/snippets/generics/brze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function createState<S>() {
let state: S

function getState() {
return state
}

function setState(x: S) {
state = x
}

return { getState, setState }
}
21 changes: 21 additions & 0 deletions snippets/snippets/generics/defo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function createState<S>() {
let state: S

function getState() {
return state
}

function setState(x: S) {
state = x
}

return { getState, setState }
}

const numState = createState<number>()
numState.setState(1)
console.log(numState.getState())

const strState = createState<string>()
strState.setState('foo')
console.log(strState.getState())
6 changes: 6 additions & 0 deletions snippets/snippets/generics/gjgg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Creates a number-only state
const numState = createState<number>()
numState.setState(1)
console.log(numState.getState())

// numState.setState('foo') will fail!
6 changes: 6 additions & 0 deletions snippets/snippets/generics/hkgv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Creates a string-only state
const strState = createState<string>()
strState.setState('foo')
console.log(strState.getState())

// strState.setState(1) will fail!
2 changes: 2 additions & 0 deletions snippets/snippets/generics/jdhu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// It sets S as number
createState<number>()
13 changes: 13 additions & 0 deletions snippets/snippets/generics/qqic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Doesn't work because the created state…
const numAndStrState = createState()

// Supports both numbers…
numAndStrState.setState(1)
console.log(numAndStrState.getState())

// And strings.
numAndStrState.setState('foo')
console.log(numAndStrState.getState())

// This is NOT what we want. We want to create
// a number-only state, and a string-only state.
6 changes: 6 additions & 0 deletions snippets/snippets/generics/rebo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// In the function definition of createState()
let state: S // <- number

function setState(x: S /* <- number */) {
state = x
}
13 changes: 13 additions & 0 deletions snippets/snippets/generics/ystu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function createState() {
let state: number | string

function getState() {
return state
}

function setState(x: number | string) {
state = x
}

return { getState, setState }
}
1 change: 1 addition & 0 deletions src/components/Caption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Caption = ({ children }: { children: React.ReactNode }) => {
padding-bottom: ${spaces(0.5)};
max-width: 18rem;
margin: 0 auto;
text-align: center;
`
]}
>
Expand Down
101 changes: 101 additions & 0 deletions src/lib/snippets.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
export const bfka = `// We want to modify createState() to support
// creating two different states:

// One that only allows numbers, and…
const numState = createState()
numState.setState(1)
console.log(numState.getState()) // 1

// The other that only allows strings.
const strState = createState()
strState.setState('foo')
console.log(strState.getState()) // foo`

export const brze = `function createState<S>() {
let state: S

function getState() {
return state
}

function setState(x: S) {
state = x
}

return { getState, setState }
}`

export const cbeq = `const { getState, setState } = createState()

setState(1)
Expand All @@ -20,6 +47,35 @@ export const cupt = `function createState() {
return { getState, setState }
}`

export const defo = `function createState<S>() {
let state: S

function getState() {
return state
}

function setState(x: S) {
state = x
}

return { getState, setState }
}

const numState = createState<number>()
numState.setState(1)
console.log(numState.getState())

const strState = createState<string>()
strState.setState('foo')
console.log(strState.getState())`

export const gjgg = `// Creates a number-only state
const numState = createState<number>()
numState.setState(1)
console.log(numState.getState())

// numState.setState('foo') will fail!`

export const gkgi = `function createState() {
// Change to string
let state: string
Expand All @@ -36,6 +92,16 @@ export const gkgi = `function createState() {
return { getState, setState }
}`

export const hkgv = `// Creates a string-only state
const strState = createState<string>()
strState.setState('foo')
console.log(strState.getState())

// strState.setState(1) will fail!`

export const jdhu = `// It sets S as number
createState<number>()`

export const kiyi = `// Confused by generics code like this?
function getProperty<T, K extends keyof T>(
obj: T,
Expand Down Expand Up @@ -78,6 +144,27 @@ const { getState, setState } = createState()
setState('foo')
console.log(getState())`

export const qqic = `// Doesn't work because the created state…
const numAndStrState = createState()

// Supports both numbers…
numAndStrState.setState(1)
console.log(numAndStrState.getState())

// And strings.
numAndStrState.setState('foo')
console.log(numAndStrState.getState())

// This is NOT what we want. We want to create
// a number-only state, and a string-only state.`

export const rebo = `// In the function definition of createState()
let state: S // <- number

function setState(x: S /* <- number */) {
state = x
}`

export const stkh = `const { getState, setState } = createState()

// What happens if we use a string instead?
Expand Down Expand Up @@ -111,6 +198,20 @@ export const xeax = `const { getState, setState } = createState()
setState('foo')
console.log(getState())`

export const ystu = `function createState() {
let state: number | string

function getState() {
return state
}

function setState(x: number | string) {
state = x
}

return { getState, setState }
}`

export const zhql = `function createState() {
let state: number

Expand Down
125 changes: 125 additions & 0 deletions src/pages/generics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,131 @@ const Page = () => (
</>
)
},
{
title: <>Challenge: Two different states</>,
content: (
<>
<P>Now that we got the basics down, here’s a challenge question:</P>
<P>
<Highlight>
Can we modify <Code>createState()</Code> such that, it can
create two different states:
</Highlight>{' '}
one that only allows numbers, and the other that only allows
strings?
</P>
<P>Here’s what I mean:</P>
<CodeBlock snippet={snippets.bfka} />
<P>
Our first <Code>createState()</Code> created number-only states,
and our second <Code>createState()</Code> created string-only
states. However, it couldn’t create both number-only states and
string-only states.
</P>
<P>
How can we modify <Code>createState()</Code> to achieve our goal?
</P>
</>
)
},
{
title: <>Attempt 1: Does this work?</>,
content: (
<>
<P>Here’s the first attempt. Does this work?</P>
<CodeBlock
snippet={snippets.ystu}
shouldHighlight={(lineNumber, tokenNumber) =>
(lineNumber === 7 && tokenNumber > 4 && tokenNumber < 10) ||
(lineNumber === 1 && tokenNumber > 4 && tokenNumber < 10)
}
/>
<P>
<strong>This does NOT work.</strong> If you use this, you’ll end
up creating a state that allows both numbers and strings, which is
not what we want.
</P>
<CodeBlock snippet={snippets.qqic} />
<P>
Instead, we want <Code>createState()</Code> to support creating
two different states: one that allows only numbers, and the other
that allows only strings.
</P>
</>
)
},
{
title: <>Attempt 2: Use generics</>,
content: (
<>
<P>
This is where <strong>generics</strong> come in. Take a look
below:
</P>
<CodeBlock
snippet={snippets.brze}
shouldHighlight={(lineNumber, tokenNumber) =>
(lineNumber === 7 && tokenNumber > 4 && tokenNumber < 8) ||
(lineNumber === 1 && tokenNumber > 4 && tokenNumber < 6) ||
(lineNumber === 0 && tokenNumber > 1 && tokenNumber < 5)
}
/>
<P>
<Code>createState()</Code> is now defined as{' '}
<Code>createState&lt;S&gt;()</Code>. You can think of{' '}
<Code>&lt;S&gt;</Code> as another argument that you have to pass
in when you call the function. But instead of passing a value, you
pass a <strong>type</strong> to it. It’s a type argument.
</P>
<P>
For example, you can pass the type <Code>number</Code> as{' '}
<Code>S</Code> when you call <Code>createState()</Code>:
</P>
<CodeBlock snippet={snippets.jdhu} />
<P>
Then, inside the function definition of <Code>createState()</Code>
, <Code>S</Code> will be come <Code>number</Code>:
</P>
<CodeBlock snippet={snippets.rebo} />
<P>
Because <Code>state</Code> will be <Code>number</Code> and{' '}
<Code>setState</Code> will only take <Code>number</Code>, it
creates a number-only state.
</P>
<CodeBlock
snippet={snippets.gjgg}
shouldHighlight={(lineNumber, tokenNumber) =>
lineNumber === 1 && tokenNumber > 5 && tokenNumber < 7
}
/>
<P>
And to create a string-only state, you can pass{' '}
<Code>number</Code> as <Code>S</Code> when you call{' '}
<Code>createState()</Code>:
</P>
<CodeBlock
snippet={snippets.hkgv}
shouldHighlight={(lineNumber, tokenNumber) =>
lineNumber === 1 && tokenNumber > 5 && tokenNumber < 7
}
/>
<P>
That’s it! And we call <Code>createState&lt;S&gt;()</Code> a
“generic function” because it’s flexible—you have a choice to make
it number-only or string-only. You know it’s a generic function if
it takes a type parameter when you call it.
</P>
<CodeBlock
snippet={snippets.brze}
caption={
<>
<Code>createState&lt;S&gt;()</Code> is a generic function
</>
}
/>
</>
)
},
underConstructionCard
]}
/>
Expand Down