├── .gitignore
├── README.md
├── components
├── Accordion.final.js
├── Accordion.js
├── Badge.final.js
├── Badge.js
├── CharacterLimit.final.js
├── CharacterLimit.js
├── CharacterSheet.final.js
├── CharacterSheet.js
├── Counter.final.js
├── Counter.js
├── CounterInput.js
├── DatePicker.final.js
├── DatePicker.js
├── Description.js
├── EffectExample.js
├── Pokemon.final.js
├── Pokemon.js
└── Todo.js
├── jsconfig.json
├── package.json
├── pages
├── exercises
│ ├── 1-badges.js
│ ├── 2-character-counter.js
│ ├── 3-effect-examples.js
│ ├── 4-date-picker.js
│ └── 5-todo.js
├── index.js
└── lessons
│ ├── 1-props-and-state.js
│ ├── 2-controlled-components.js
│ ├── 3-effects.js
│ ├── 4-compound-components.js
│ └── 5-reducers.js
├── public
└── favicon.ico
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
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
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## React Hooks Workshop
2 |
3 | Go ahead and clone this repo, and then run:
4 |
5 | ```
6 | yarn install
7 |
8 | // or
9 |
10 | npm install
11 | ```
12 |
13 | To run the rest of the content, you'll do:
14 |
15 | ```
16 | yarn run dev
17 |
18 | // or
19 |
20 | npm run dev
21 |
22 | ```
23 |
24 | ...and then you'll open up your localhost:3000 (or other port if your computer changes it).
25 |
26 | We'll be live-coding the entire time, so get your fingers ready!
27 |
--------------------------------------------------------------------------------
/components/Accordion.final.js:
--------------------------------------------------------------------------------
1 | import { Fragment, useState, useContext, createContext } from 'react'
2 | import Description from '@components/Description'
3 |
4 | function Accordion({ data }) {
5 | const [activeIndex, setActiveIndex] = useState(0)
6 | return (
7 |
8 | {data.map((tab, index) => {
9 | const isActive = index === activeIndex
10 | return (
11 |
12 |
setActiveIndex(index)}
16 | >
17 | {tab.label}
18 | {tab.icon}
19 |
20 |
21 | )
22 | })}
23 |
24 | )
25 | }
26 |
27 | let AccordionContext = createContext()
28 |
29 | function AccordionCC({ children }) {
30 | let [activeIndex, setActiveIndex] = useState(0)
31 | return (
32 |
33 | {children.map((child, index) => {
34 | return (
35 |
39 | {child}
40 |
41 | )
42 | })}
43 |
44 | )
45 | }
46 |
47 | let SectionContext = createContext()
48 |
49 | function Section({ children, disabled }) {
50 | return (
51 |
52 | {children}
53 |
54 | )
55 | }
56 |
57 | function Title({ children }) {
58 | let { index, activeIndex, setActiveIndex } = useContext(AccordionContext)
59 | let isActive = index === activeIndex
60 | let { disabled } = useContext(SectionContext)
61 | return (
62 | {
65 | if (!disabled) setActiveIndex(index)
66 | }}
67 | className={disabled ? 'disabled' : isActive ? 'expanded' : ''}
68 | >
69 | {children}
70 |
71 | )
72 | }
73 |
74 | function Content({ children }) {
75 | let { index, activeIndex } = useContext(AccordionContext)
76 | let isActive = index === activeIndex
77 | return (
78 |
79 | {children}
80 |
81 | )
82 | }
83 |
84 | function App() {
85 | return (
86 |
87 |
88 |
89 |
90 | Paris 🧀
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Lech ⛷
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | Madrid 🍷
107 |
108 |
109 |
110 |
111 |
112 |
113 |
181 |
182 | )
183 | }
184 |
185 | export default App
186 |
--------------------------------------------------------------------------------
/components/Accordion.js:
--------------------------------------------------------------------------------
1 | import { Fragment, useState } from 'react'
2 | import Description from '@components/Description'
3 |
4 | function Accordion({ data }) {
5 | const [activeIndex, setActiveIndex] = useState(0)
6 | return (
7 |
8 | {data.map((tab, index) => {
9 | const isActive = index === activeIndex
10 | return (
11 |
12 | setActiveIndex(index)}
16 | >
17 | {tab.label}
18 | {tab.icon}
19 |
20 |
21 | {tab.content}
22 |
23 |
24 | )
25 | })}
26 |
27 | )
28 | }
29 |
30 | function App() {
31 | const data = [
32 | {
33 | label: 'Paris',
34 | icon: '🧀',
35 | content: ,
36 | },
37 | {
38 | label: 'Lech',
39 | icon: '⛷',
40 | content: ,
41 | },
42 | {
43 | label: 'Madrid',
44 | icon: '🍷',
45 | content: ,
46 | },
47 | ]
48 |
49 | return (
50 |
121 | )
122 | }
123 |
124 | export default App
125 |
--------------------------------------------------------------------------------
/components/Badge.final.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | const Badge = ({ children, color, dismissable }) => {
4 | const [dismissed, setDismissed] = useState(false)
5 |
6 | if (dismissed) return null
7 |
8 | return (
9 | {
12 | if (dismissable) setDismissed(true)
13 | }}
14 | >
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export default function BadgeList() {
21 | return (
22 |
23 | Oh boy, statuses!
24 | Success This is operational.
25 |
26 | Removed
27 | {' '}
28 | This is critical.
29 |
30 | Warning
31 | {' '}
32 | This is a warning.
33 | Beta This is in progress.
34 |
68 |
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/components/Badge.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | // Change this file so that:
4 | // 1) The Badge Component takes in the correct props
5 | // 2) If a badge is dismissable, when you click on it, it renders as null
6 |
7 | // Hints:
8 | // 1) Use the provided styles to guide you!
9 | // 2) Remember useState() returns [someState, setSomeState]
10 |
11 | // Bonus points: Try making it so the look of the badge changes somehow
12 | // when you hover over it, perhaps an x for if you're about to dismiss it?
13 |
14 | const Badge = () => {
15 | return Hello, world!
16 | }
17 |
18 | export default function BadgeList() {
19 | return (
20 |
21 | Oh boy, statuses!
22 | Success This is operational.
23 |
24 | Removed
25 | {' '}
26 | This is critical.
27 |
28 | Warning
29 | {' '}
30 | This is a warning.
31 | Beta This is in progress.
32 |
66 |
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/components/CharacterLimit.final.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | const CharacterLimitInput = ({ text, defaults }) => {
4 | const [message, setMessage] = useState('')
5 | const maxLength = 20
6 |
7 | return (
8 | maxLength ? 'tooLong' : ''}`}
10 | >
11 |
12 | {defaults.map((b) => {
13 | return (
14 | {
17 | setMessage(b)
18 | }}
19 | >
20 | {b}
21 |
22 | )
23 | })}
24 |
25 |
36 | )
37 | }
38 |
39 | export default function MoodTracker() {
40 | let defaultMoods = ['Great', 'Okay', 'Bad']
41 |
42 | return (
43 |
44 | Mood Tracker
45 |
46 |
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/components/CharacterLimit.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | // Change this file so that:
4 | // 1) The textarea is a controlled component
5 | // 2) The character counter at the bottom is functional
6 | // 3) Clicking the buttons populates the textarea
7 |
8 | // Note: Notice the CSS classes for if the input is too long!
9 |
10 | const CharacterLimitInput = ({ text, defaults }) => {
11 | const maxLength = 140
12 |
13 | return (
14 |
15 |
16 | {defaults.map((b) => {
17 | return {b}
18 | })}
19 |
20 |
21 |
0/{maxLength}
22 |
23 | )
24 | }
25 |
26 | export default function MoodTracker() {
27 | let defaultMoods = ['Great', 'Okay', 'Bad']
28 |
29 | return (
30 |
31 | Mood Tracker
32 |
33 |
66 |
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/components/CharacterSheet.final.js:
--------------------------------------------------------------------------------
1 | import { useState, useReducer } from 'react'
2 | import friendlyWords from 'friendly-words'
3 |
4 | let backgrounds = [
5 | 'Noble',
6 | 'Urchin',
7 | 'Folk Hero',
8 | 'Acolyte',
9 | 'Criminal',
10 | 'Hermit',
11 | 'Guild Artisan',
12 | 'Sage',
13 | ]
14 |
15 | function randomBackground() {
16 | return backgrounds[Math.floor(Math.random() * backgrounds.length)]
17 | }
18 |
19 | function randomName() {
20 | let array = friendlyWords.predicates
21 | let string = array[Math.floor(Math.random() * array.length)]
22 | return string.charAt(0).toUpperCase() + string.slice(1)
23 | }
24 |
25 | function useMyReducer() {
26 | let [state, dispatch] = useReducer(
27 | (state, action) => {
28 | switch (action.type) {
29 | case 'TOGGLE_DARK_MODE': {
30 | return {
31 | ...state,
32 | darkMode: !state.darkMode,
33 | }
34 | }
35 | case 'BG_SELECT': {
36 | return {
37 | ...state,
38 | background: action.value,
39 | error: !backgrounds.includes(action.value)
40 | ? 'This background does NOT exist.'
41 | : null,
42 | }
43 | }
44 | case 'NAME_CHARACTER': {
45 | return {
46 | ...state,
47 | name: action.name,
48 | error:
49 | action.name.length > 15
50 | ? 'That name is way too long, bucko'
51 | : null,
52 | }
53 | }
54 | case 'RANDOM_VALUES': {
55 | return {
56 | ...state,
57 | name: randomName(),
58 | background: randomBackground(),
59 | }
60 | }
61 | case 'DISMISS_ERROR': {
62 | return { ...state, error: null }
63 | }
64 | default: {
65 | return state
66 | }
67 | }
68 | },
69 | {
70 | darkMode: false,
71 | name: '',
72 | background: '',
73 | error: null,
74 | }
75 | )
76 | return [state, dispatch]
77 | }
78 |
79 | export default function App() {
80 | let [{ darkMode, name, background, error }, dispatch] = useMyReducer()
81 |
82 | function handleBackgroundSelect(event) {
83 | dispatch({ type: 'BG_SELECT', value: event.target.value })
84 | }
85 |
86 | return (
87 | <>
88 |
89 |
{
91 | dispatch({ type: 'TOGGLE_DARK_MODE' })
92 | }}
93 | >
94 | Dark Mode {darkMode ? 'ON' : 'OFF'}
95 | {' '}
96 |
97 |
{
102 | dispatch({
103 | type: 'NAME_CHARACTER',
104 | name: event.target.value,
105 | })
106 | }}
107 | />
108 |
109 | {backgrounds.map((b) => {
110 | return {b}
111 | })}
112 |
113 | {error && (
114 |
115 | {error}
116 | {
118 | dispatch({ type: 'DISMISS_ERROR' })
119 | }}
120 | >
121 | Dismiss
122 |
123 |
124 | )}
125 |
126 |
Name: {name}
127 | Background: {background}
128 |
129 |
{
131 | dispatch({ type: 'RANDOM_VALUES' })
132 | }}
133 | >
134 | Do it all for me instead
135 |
136 |
137 |
196 | >
197 | )
198 | }
199 |
--------------------------------------------------------------------------------
/components/CharacterSheet.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import friendlyWords from 'friendly-words'
3 |
4 | let backgrounds = [
5 | 'Noble',
6 | 'Urchin',
7 | 'Folk Hero',
8 | 'Acolyte',
9 | 'Criminal',
10 | 'Hermit',
11 | 'Guild Artisan',
12 | 'Sage',
13 | ]
14 |
15 | function randomBackground() {
16 | return backgrounds[Math.floor(Math.random() * backgrounds.length)]
17 | }
18 |
19 | function randomName() {
20 | let array = friendlyWords.predicates
21 | let string = array[Math.floor(Math.random() * array.length)]
22 | return string.charAt(0).toUpperCase() + string.slice(1)
23 | }
24 |
25 | export default function App() {
26 | let [darkMode, setDarkMode] = useState(false)
27 | let [name, setName] = useState('')
28 | let [background, setBackground] = useState('')
29 | let [error, setError] = useState(null)
30 |
31 | function handleBackgroundSelect(event) {
32 | let value = event.target.value
33 | setBackground(value)
34 | if (!backgrounds.includes(value)) {
35 | setError('This background does NOT exist.')
36 | } else {
37 | setError(null)
38 | }
39 | }
40 |
41 | return (
42 | <>
43 |
44 |
{
46 | setDarkMode(!darkMode)
47 | }}
48 | >
49 | Dark Mode {darkMode ? 'ON' : 'OFF'}
50 | {' '}
51 |
52 |
{
57 | setName(event.target.value)
58 | if (event.target.value.length > 15) {
59 | setError('Name is WAY too long, bucko.')
60 | }
61 | }}
62 | />
63 |
64 | {backgrounds.map((b) => {
65 | return {b}
66 | })}
67 |
68 | {error && (
69 |
70 | {error}
71 | {
73 | setError(null)
74 | }}
75 | >
76 | Dismiss
77 |
78 |
79 | )}
80 |
81 |
Name: {name}
82 | Background: {background}
83 |
84 |
{
86 | setName(randomName())
87 | setBackground(randomBackground())
88 | }}
89 | >
90 | Do it all for me instead
91 |
92 |
93 |
152 | >
153 | )
154 | }
155 |
--------------------------------------------------------------------------------
/components/Counter.final.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export default function Counter() {
4 | const [count, setCount] = useState(1)
5 |
6 | const handleAdd = () => setCount(count + 1)
7 | const handleSubtract = () => setCount(count - 1)
8 |
9 | return (
10 |
11 |
+
12 |
{count}
13 |
-
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/components/Counter.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | const Button = ({ children, ...props }) => {
4 | return {children}
5 | }
6 |
7 | export default function Counter() {
8 | let count = 0
9 |
10 | return Let's count: {count}
11 | }
12 |
--------------------------------------------------------------------------------
/components/CounterInput.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export default function Counter() {
4 | const [count, setCount] = useState(1)
5 |
6 | const handleAdd = () => setCount(count + 1)
7 | const handleSubtract = () => setCount(count - 1)
8 |
9 | return (
10 |
11 | -
12 |
13 | +
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/components/DatePicker.final.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, createContext } from 'react'
2 |
3 | const DateFieldsContext = createContext()
4 |
5 | export default function DateFields({ children, value, onChange }) {
6 | let date = value
7 | return (
8 |
9 |
10 | {children}
11 |
12 |
21 |
22 | )
23 | }
24 |
25 | export function DayField(props) {
26 | const { date, onChange } = useContext(DateFieldsContext)
27 | const value = date.getDate()
28 | const handleChange = (event) => {
29 | const newDate = new Date(date.getTime())
30 | newDate.setDate(parseInt(event.target.value))
31 | onChange(newDate)
32 | }
33 |
34 | return (
35 |
36 | {Array.from({ length: 31 }).map((_, index) => (
37 |
38 | {index + 1}
39 |
40 | ))}
41 |
42 | )
43 | }
44 |
45 | export function MonthField(props) {
46 | const { date, onChange } = useContext(DateFieldsContext)
47 | const month = date.getMonth()
48 | const handleChange = (event) => {
49 | const newDate = new Date(date.getTime())
50 | newDate.setMonth(parseInt(event.target.value))
51 | onChange(newDate)
52 | }
53 |
54 | return (
55 |
56 | {Array.from({ length: 12 }).map((_, index) => (
57 |
58 | {index + 1}
59 |
60 | ))}
61 |
62 | )
63 | }
64 |
65 | export function YearField(props) {
66 | const { date, onChange } = useContext(DateFieldsContext)
67 | const { start, end } = props
68 | const years = Array.from({
69 | length: end - start + 1,
70 | }).map((_, index) => index + start)
71 | const handleChange = (event) => {
72 | const newDate = new Date(date.getTime())
73 | newDate.setYear(parseInt(event.target.value), 1)
74 | onChange(newDate)
75 | }
76 |
77 | return (
78 |
79 | {years.map((year) => (
80 | {year}
81 | ))}
82 |
83 | )
84 | }
85 |
--------------------------------------------------------------------------------
/components/DatePicker.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, createContext } from 'react'
2 |
3 | // Right now in the file 4-date-picker.js, we call these components with:
4 | //
10 |
11 | // We want to change it to:
12 | //
13 | //
14 | //
15 | //
16 | //
17 |
18 | // The tasks:
19 | // 1) Edit 4-date-picker.js to stop rendering the individual components
20 | // in DateFields and render children instead
21 | // 2) Provide Context in DateFields
22 | // 3) Use the Context in MonthField, DayField, and YearField.
23 |
24 | export default function DateFields({ start, end, value, onChange }) {
25 | return (
26 |
27 | /
28 | /
29 |
30 |
39 |
40 | )
41 | }
42 |
43 | export function DayField(props) {
44 | const { date, onChange } = props
45 | const value = date.getDate()
46 | const handleChange = (event) => {
47 | const newDate = new Date(date.getTime())
48 | newDate.setDate(parseInt(event.target.value))
49 | onChange(newDate)
50 | }
51 |
52 | return (
53 |
54 | {Array.from({ length: 31 }).map((_, index) => (
55 |
56 | {index + 1}
57 |
58 | ))}
59 |
60 | )
61 | }
62 |
63 | export function MonthField(props) {
64 | const { date, onChange } = props
65 | const month = date.getMonth()
66 | const handleChange = (event) => {
67 | const newDate = new Date(date.getTime())
68 | newDate.setMonth(parseInt(event.target.value))
69 | onChange(newDate)
70 | }
71 |
72 | return (
73 |
74 | {Array.from({ length: 12 }).map((_, index) => (
75 |
76 | {index + 1}
77 |
78 | ))}
79 |
80 | )
81 | }
82 |
83 | export function YearField(props) {
84 | const { date, onChange, start, end } = props
85 | const years = Array.from({ length: end - start + 1 }).map(
86 | (_, index) => index + start
87 | )
88 | const handleChange = (event) => {
89 | const newDate = new Date(date.getTime())
90 | newDate.setYear(parseInt(event.target.value), 1)
91 | onChange(newDate)
92 | }
93 |
94 | return (
95 |
96 | {years.map((year) => (
97 | {year}
98 | ))}
99 |
100 | )
101 | }
102 |
--------------------------------------------------------------------------------
/components/Description.js:
--------------------------------------------------------------------------------
1 | function Description({ city }) {
2 | const data = {
3 | paris:
4 | "Paris is the capital and most populous city of France, with a population of 2,148,271 residents. Since the 17th century, Paris has been one of Europe's major centres of finance, diplomacy, commerce, fashion, science and the arts.",
5 | lech:
6 | 'Lech am Arlberg is a mountain village and an exclusive ski resort in the Bludenz district in the Austrian state of Vorarlberg on the banks of the river Lech.',
7 | madrid:
8 | 'Madrid is the capital and most populous city of Spain. As the capital city of Spain, seat of government, and residence of the Spanish monarch, Madrid is also the political, economic and cultural centre of the country.',
9 | }
10 |
11 | return {data[city]}
12 | }
13 |
14 | export default Description
15 |
--------------------------------------------------------------------------------
/components/EffectExample.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | // Good example API to practice with:
4 | // https://api.github.com/users/username/repos
5 |
6 | // Good non-API example hook:
7 | function useMedia(query) {
8 | const [matches, setMatches] = useState(window.matchMedia(query).matches)
9 |
10 | useEffect(() => {
11 | const media = window.matchMedia(query)
12 | if (media.matches !== matches) {
13 | setMatches(media.matches)
14 | }
15 | const listener = () => {
16 | setMatches(media.matches)
17 | }
18 | media.addListener(listener)
19 | return () => media.removeListener(listener)
20 | }, [matches, query])
21 |
22 | return matches
23 | }
24 |
25 | export default function EffectExample() {
26 | return (
27 | <>
28 | Look at the code to look at the examples! EffectExample.js :)
29 | >
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/components/Pokemon.final.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | export default function Pokemon() {
4 | const [pokémon, setPokémon] = useState('pikachu')
5 | const [img, setImg] = useState(null)
6 | const [error, setError] = useState(null)
7 |
8 | useEffect(() => {
9 | let isCurrent = true
10 | fetch(`https://pokeapi.co/api/v2/pokemon/${pokémon}/`)
11 | .then((res) => res.json())
12 | .then((res) => {
13 | if (isCurrent) {
14 | setImg(res.sprites.front_default)
15 | }
16 | })
17 | .catch((error) => {
18 | setError(error)
19 | })
20 | return () => {
21 | isCurrent = false
22 | }
23 | }, [pokémon])
24 |
25 | return (
26 | <>
27 |
28 |
29 | setPokémon(e.target.value)} defaultValue={pokémon} type="text" />
30 |
31 |
Hello, {pokémon}!
32 | {img !== null &&
}
33 |
34 |
35 |
83 | >
84 | )
85 | }
86 |
--------------------------------------------------------------------------------
/components/Pokemon.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | export default function Pokemon() {
4 | const [pokémon, setPokémon] = useState('pikachu')
5 |
6 | return (
7 | <>
8 |
9 |
10 | setPokémon(e.target.value)} defaultValue={pokémon} type="text" />
11 |
12 |
Hello, {pokémon}!
13 |
14 |
15 |
65 | >
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/components/Todo.js:
--------------------------------------------------------------------------------
1 | import { useState, useReducer } from 'react'
2 |
3 | // Your task is to convert the useStates here into useReducer!
4 |
5 | export default function Todo() {
6 | const [items, setItems] = useState([])
7 | const [newItem, setNewItem] = useState('')
8 |
9 | const addItem = (thingToAdd) => {
10 | setItems(items.concat(thingToAdd))
11 | }
12 |
13 | const removeItem = (thingToRemove) => {
14 | setItems(
15 | items.filter((item) => {
16 | return item !== thingToRemove
17 | })
18 | )
19 | }
20 |
21 | return (
22 | <>
23 |
24 |
To Do List
25 |
40 |
41 | {items.map((item) => {
42 | return (
43 |
44 | {item}{' '}
45 | {
47 | removeItem(item)
48 | }}
49 | >
50 | x
51 |
52 |
53 | )
54 | })}
55 |
56 |
57 |
70 | >
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@components/*": ["components/*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hooks-workshop",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "friendly-words": "^1.1.10",
12 | "next": "9.4.2",
13 | "react": "16.13.1",
14 | "react-dom": "16.13.1"
15 | },
16 | "prettier": {
17 | "printWidth": 80,
18 | "semi": false,
19 | "singleQuote": true,
20 | "tabWidth": 2
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pages/exercises/1-badges.js:
--------------------------------------------------------------------------------
1 | import BadgeList from '@components/Badge'
2 |
3 | export default function Badges() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/exercises/2-character-counter.js:
--------------------------------------------------------------------------------
1 | import CharacterLimit from '@components/CharacterLimit.js'
2 |
3 | export default function Badges() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/exercises/3-effect-examples.js:
--------------------------------------------------------------------------------
1 | import EffectExample from '@components/EffectExample.js'
2 |
3 | export default function Badges() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/exercises/4-date-picker.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import DateFields from '@components/DatePicker.js'
3 |
4 | export default function Dates() {
5 | const [startDate, setStartDate] = useState(new Date('May 28, 2020'))
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/pages/exercises/5-todo.js:
--------------------------------------------------------------------------------
1 | import Todo from '@components/Todo.js'
2 |
3 | export default function TodoApp() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
Welcome to the workshop!
7 |
37 |
38 | Exercises:
39 |
40 |
67 |
68 |
73 |
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/pages/lessons/1-props-and-state.js:
--------------------------------------------------------------------------------
1 | // import Counter from '@components/Counter'
2 |
3 | export default function PropsState() {
4 | return null
5 | }
6 |
--------------------------------------------------------------------------------
/pages/lessons/2-controlled-components.js:
--------------------------------------------------------------------------------
1 | import CounterInput from '@components/CounterInput'
2 |
3 | export default function Controlled() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/lessons/3-effects.js:
--------------------------------------------------------------------------------
1 | import Pokemon from '@components/Pokemon'
2 |
3 | export default function Controlled() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/lessons/4-compound-components.js:
--------------------------------------------------------------------------------
1 | import Accordion from '@components/Accordion'
2 |
3 | export default function Compound() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/pages/lessons/5-reducers.js:
--------------------------------------------------------------------------------
1 | import CharacterSheet from '@components/CharacterSheet'
2 |
3 | export default function Reducers() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cassidoo/react-hooks-workshop-2020/9002afd9d1c87f5ad0da9dcf87eaa91baab3879e/public/favicon.ico
--------------------------------------------------------------------------------