Skip to content

Commit 3bf1940

Browse files
Finish Video
1 parent a0c374a commit 3bf1940

File tree

13 files changed

+176
-3
lines changed

13 files changed

+176
-3
lines changed

src/13-useEventListener/useEventListener.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function useEventListener(
1212
}, [callback])
1313

1414
useEffect(() => {
15+
if (element == null) return
1516
const handler = e => callbackRef.current(e)
1617
element.addEventListener(eventType, handler)
1718

src/14-useOnScreen/useOnScreen.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export default function useOnScreen(ref, rootMargin = "0px") {
44
const [isVisible, setIsVisible] = useState(false)
55

66
useEffect(() => {
7-
console.log(ref.current)
87
if (ref.current == null) return
98
const observer = new IntersectionObserver(
109
([entry]) => setIsVisible(entry.isIntersecting),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import useMediaQuery from "./useMediaQuery"
2+
3+
export default function MediaQueryComponent() {
4+
const isLarge = useMediaQuery("(min-width: 200px)")
5+
6+
return <div>Large: {isLarge.toString()}</div>
7+
}

src/16-useMediaQuery/useMediaQuery.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useState, useEffect } from "react"
2+
import useEventListener from "../13-useEventListener/useEventListener"
3+
4+
export default function useMediaQuery(mediaQuery) {
5+
const [isMatch, setIsMatch] = useState(false)
6+
const [mediaQueryList, setMediaQueryList] = useState(null)
7+
8+
useEffect(() => {
9+
const list = window.matchMedia(mediaQuery)
10+
setMediaQueryList(list)
11+
setIsMatch(list.matches)
12+
}, [mediaQuery])
13+
14+
useEventListener("change", e => setIsMatch(e.matches), mediaQueryList)
15+
16+
return isMatch
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useRef } from "react"
2+
import useGeolocation from "./useGeolocation"
3+
4+
export default function GeolocationComponent() {
5+
const ref = useRef()
6+
const {
7+
loading,
8+
error,
9+
data: { latitude, longitude },
10+
} = useGeolocation(ref)
11+
12+
return (
13+
<>
14+
<div>Loading: {loading.toString()}</div>
15+
<div>Error: {error?.message}</div>
16+
<div>
17+
{latitude} x {longitude}
18+
</div>
19+
</>
20+
)
21+
}
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useState, useEffect } from "react"
2+
3+
export default function useGeolocation(options) {
4+
const [loading, setLoading] = useState(true)
5+
const [error, setError] = useState()
6+
const [data, setData] = useState({})
7+
8+
useEffect(() => {
9+
const successHandler = e => {
10+
setLoading(false)
11+
setError(null)
12+
setData(e.coords)
13+
}
14+
const errorHandler = e => {
15+
setError(e)
16+
setLoading(false)
17+
}
18+
navigator.geolocation.getCurrentPosition(
19+
successHandler,
20+
errorHandler,
21+
options
22+
)
23+
const id = navigator.geolocation.watchPosition(
24+
successHandler,
25+
errorHandler,
26+
options
27+
)
28+
return () => navigator.geolocation.clearWatch(id)
29+
}, [options])
30+
31+
return { loading, error, data }
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import useStateWithValidation from "./useStateWithValidation"
2+
3+
export default function StateWithValidationComponent() {
4+
const [username, setUsername, isValid] = useStateWithValidation(
5+
name => name.length > 5,
6+
""
7+
)
8+
9+
return (
10+
<>
11+
<div>Valid: {isValid.toString()}</div>
12+
<input
13+
type="text"
14+
value={username}
15+
onChange={e => setUsername(e.target.value)}
16+
/>
17+
</>
18+
)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useState, useCallback } from "react"
2+
3+
export default function useStateWithValidation(validationFunc, initialValue) {
4+
const [state, setState] = useState(initialValue)
5+
const [isValid, setIsValid] = useState(() => validationFunc(state))
6+
7+
const onChange = useCallback(
8+
nextState => {
9+
const value =
10+
typeof nextState === "function" ? nextState(state) : nextState
11+
setState(value)
12+
setIsValid(validationFunc(value))
13+
},
14+
[validationFunc]
15+
)
16+
17+
return [state, onChange, isValid]
18+
}

src/19-useSize/SizeComponent.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useRef } from "react"
2+
import useSize from "./useSize"
3+
4+
export default function SizeComponent() {
5+
const ref = useRef()
6+
const size = useSize(ref)
7+
8+
return (
9+
<>
10+
<div>{JSON.stringify(size)}</div>
11+
<textarea ref={ref}></textarea>
12+
</>
13+
)
14+
}

src/19-useSize/useSize.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useState } from "react"
2+
import { useEffect } from "react/cjs/react.development"
3+
4+
export default function useSize(ref) {
5+
const [size, setSize] = useState({})
6+
7+
useEffect(() => {
8+
if (ref.current == null) return
9+
const observer = new ResizeObserver(([entry]) => setSize(entry.contentRect))
10+
observer.observe(ref.current)
11+
return () => observer.disconnect()
12+
}, [])
13+
14+
return size
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useState } from "react"
2+
import useEffectOnce from "./useEffectOnce"
3+
4+
export default function EffectOnceComponent() {
5+
const [count, setCount] = useState(0)
6+
7+
useEffectOnce(() => alert("Hi"))
8+
9+
return (
10+
<>
11+
<div>{count}</div>
12+
<button onClick={() => setCount(c => c + 1)}>Increment</button>
13+
</>
14+
)
15+
}

src/20-useEffectOnce/useEffectOnce.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { useEffect } from "react"
2+
3+
export default function useEffectOnce(cb) {
4+
useEffect(cb, [])
5+
}

src/App.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import DeepCompareEffectComponent from "./12-useDeepCompareEffect/DeepCompareEff
1313
import EventListenerComponent from "./13-useEventListener/EventListenerComponent"
1414
import OnScreenComponentComponent from "./14-useOnScreen/OnScreenComponent"
1515
import WindowSizeComponent from "./15-useWindowSize/WindowSizeComponent"
16+
import MediaQueryComponent from "./16-useMediaQuery/MediaQueryComponent"
17+
import GeolocationComponent from "./17-useGeolocation/GeolocationComponent"
18+
import StateWithValidationComponent from "./18-useStateWithValidation/StateWithValidationComponent"
19+
import SizeComponent from "./19-useSize/SizeComponent"
20+
import EffectOnceComponent from "./20-useEffectOnce/EffectOnceComponent"
1621

1722
function App() {
1823
// return <ToggleComponent />
@@ -28,8 +33,13 @@ function App() {
2833
// return <ScriptComponent />
2934
// return <DeepCompareEffectComponent />
3035
// return <EventListenerComponent />
31-
// return <OnScreenComponentComponent />
32-
return <WindowSizeComponent />
36+
return <OnScreenComponentComponent />
37+
// return <WindowSizeComponent />
38+
// return <MediaQueryComponent />
39+
// return <GeolocationComponent />
40+
// return <StateWithValidationComponent />
41+
// return <SizeComponent />
42+
// return <EffectOnceComponent />
3343
}
3444

3545
export default App

0 commit comments

Comments
 (0)