├── README.md ├── useAxios.js ├── useBeforeLeave.js ├── useClick.js ├── useConfirm.js ├── useFadeIn.js ├── useFullscreen.js ├── useHover.js ├── useInput.js ├── useNetwork.js ├── useNotification.js ├── usePreventLeave.js ├── useScroll.js ├── useTabs.js └── useTitle.js /README.md: -------------------------------------------------------------------------------- 1 | ## 📢 목적 2 | 3 | 해당 프로젝트는 노마드 코더 "실전형 리액트 Hooks 10개" 강의를 수강하여 리액트를 공부하는 프로젝트입니다.
4 | 해당 코드는 https://codesandbox.io/ 를 이용하여 제작되었습니다. 5 | 6 | ## 📢 공부 내용 7 | 8 |
9 | 10 | 📑 Hook 11 | 12 | ### Hook이란? 13 | 14 | 함수 컴포넌트에서 React state와 Lifecycle 기능을 연동할 수 있게 해주는 함수 (버전 16.8부터 도입) 15 | 16 | ### Hook의 사용 규칙 17 | 18 | - 최상위에서만 Hook 호출이 가능 19 | - 리액트 함수 컴포넌트 내에서만 호출이 가능하며, 일반 자바스크립트 함수 안에서는 호출하면 안됨 (custom hook에서는 가능) 20 | 21 | ### Hook이 만들어진 이유 22 | 23 | - 컴포넌트들 사이에서 상태 로직을 재사용하는 것의 어려움 24 | - 복잡한 컴포넌트는 이해하기 어려움 25 | - Class 컴포넌트는 인간과 기계 모두를 혼란스럽게 함 26 | 27 | ### 참고 사이트 28 | 29 | [Hook 소개](https://ko.reactjs.org/docs/hooks-intro.html)
30 | 31 |
32 | 33 |
34 | 35 | 📑 useState 36 | 37 | ### useState란? 38 | 39 | 기존 class 컴포넌트에서 사용하던 this.state와 동일한 역할을 한다.
40 | state 변수와 state를 업데이트 하는 함수, 두 가지 쌍을 반환한다. 41 | ``` javascript 42 | const [age, setAge] = useState(20); 43 | ``` 44 | 위와 같은 표현은 구조 분해 할당이라고 한다. 45 | 46 | - 함수 47 | ``` javascript 48 | function App() { 49 | const [item, setItem] = useState(1); 50 | const incrementItem = () => setItem(item + 1); 51 | const decrementItem = () => setItem(item - 1); 52 | return ( 53 |
54 |

Hello {item}

55 |

Start editing to see some magic happen!

56 | 57 | 58 |
59 | ); 60 | } 61 | ``` 62 | 63 | - 클래스 64 | ``` javascript 65 | class App extends React.Component { 66 | state = { 67 | item: 1 68 | }; 69 | 70 | render() { 71 | const { item } = this.state; 72 | return ( 73 |
74 |

Hello {item}

75 |

Start editing to see some magic happen!

76 | 77 | 78 |
79 | ); 80 | } 81 | 82 | incrementItem = () => { 83 | this.setState((state) => { 84 | return { 85 | item: state.item + 1 86 | }; 87 | }); 88 | }; 89 | 90 | decrementItem = () => { 91 | this.setState((state) => { 92 | return { 93 | item: state.item + 1 94 | }; 95 | }); 96 | }; 97 | } 98 | ``` 99 | 100 | ### 참고 사이트 101 | 102 | [useState 가이드](https://ko.reactjs.org/docs/hooks-state.html)
103 | 104 |
105 | 106 | ## 📢 Custom Hooks 107 | 108 |
109 | 110 | 📑 useInput 111 | 112 | ### useInput이란? 113 | 114 | input 역활을 제어 하는 것 115 | 116 | ``` javascript 117 | const useInput = (initialValue, validator) => { 118 | const [value, setValue] = useState(initialValue); 119 | const onChange = (event) => { 120 | const { 121 | target: { value } 122 | } = event; 123 | let willUpdate = true; 124 | if (typeof validator === "function") { 125 | willUpdate = validator(value); 126 | } 127 | if (willUpdate) { 128 | setValue(value); 129 | } 130 | }; 131 | return { value, onChange }; 132 | }; 133 | 134 | function App() { 135 | const maxLen = (value) => value.length < 10; 136 | const name = useInput("your name", maxLen); 137 | return ( 138 |
139 |

Hello

140 | 141 |
142 | ); 143 | } 144 | ``` 145 | 146 |
147 | 148 |
149 | 150 | 📑 useTabs 151 | 152 | ### useTabs이란? 153 | 154 | 웹사이트에 메뉴 또는 무엇이든 간에 tab을 사용하기 매우 쉽게 만들어주는 것 155 | 156 | ``` javascript 157 | const useTabs = (initialTab, allTabs) => { 158 | if (!allTabs || !Array.isArray(allTabs)) { 159 | return; 160 | } 161 | const [currentIndex, setCurrentIndex] = useState(initialTab); 162 | return { 163 | currnetItem: allTabs[currentIndex], 164 | changeItem: setCurrentIndex 165 | }; 166 | }; 167 | 168 | const content = [ 169 | { 170 | tab: "Section 1", 171 | content: "I'm the content of the Section 1" 172 | }, 173 | { 174 | tab: "Section 2", 175 | content: "I'm the content of the Section 2" 176 | } 177 | ]; 178 | 179 | function App() { 180 | const { currnetItem, changeItem } = useTabs(0, content); 181 | return ( 182 |
183 |

Hello

184 | {content.map((section, index) => ( 185 | 188 | ))} 189 |
{currnetItem.content}
190 |
191 | ); 192 | } 193 | ``` 194 | 195 |
196 | 197 |
198 | 199 | 📑 useTitle 200 | 201 | ### useTitle이란? 202 | 203 | react document의 title을 몇개의 hoots와 함께 바꾸는 것 204 | 205 | ``` javascript 206 | const useTitle = (initialTitle) => { 207 | const [title, setTitle] = useState(initialTitle); 208 | const updateTitle = () => { 209 | const htmlTitle = document.querySelector("title"); 210 | htmlTitle.innerText = title; 211 | }; 212 | useEffect(updateTitle, [title]); 213 | return setTitle; 214 | }; 215 | 216 | function App() { 217 | const titleUpdater = useTitle("Loading..."); 218 | setTimeout(() => titleUpdater("home"), 5000); 219 | return ( 220 |
221 |

Hello

222 |
223 | ); 224 | } 225 | ``` 226 | 227 |
228 | 229 |
230 | 231 | 📑 useClick 232 | 233 | ### useClick이란? 234 | 235 | 유저가 element를 클릭한 시점으로 이벤트 주기 236 | 237 | ``` javascript 238 | const useClick = (onClick) => { 239 | if (typeof onclick !== "function") { 240 | return; 241 | } 242 | const element = useRef(); 243 | useEffect(() => { 244 | if (element.current) { 245 | element.current.addEventListener("click", onClick); 246 | } 247 | return () => { 248 | if (element.current) { 249 | element.current.removeEventListener("click", onClick); 250 | } 251 | }; 252 | }, []); 253 | return element; 254 | }; 255 | 256 | function App() { 257 | const onClick = () => console.log("hello"); 258 | const title = useClick(onClick); 259 | return ( 260 |
261 |

Hello

262 |
263 | ); 264 | } 265 | ``` 266 | 267 |
268 | 269 |
270 | 271 | 📑 useHover 272 | 273 | ### useHover이란? 274 | 275 | 유저가 element를 호버한 시점으로 이벤트 주기 276 | 277 | ``` javascript 278 | const useHover = (onHover) => { 279 | if (typeof onHover !== "function") { 280 | return; 281 | } 282 | const element = useRef(); 283 | useEffect(() => { 284 | if (element.current) { 285 | element.current.addEventListener("mouseenter", onHover); 286 | } 287 | return () => { 288 | if (element.current) { 289 | element.current.removeEventListener("mouseenter", onHover); 290 | } 291 | }; 292 | }, []); 293 | return element; 294 | }; 295 | 296 | function App() { 297 | const onHover = () => console.log("hello"); 298 | const title = useHover(onHover); 299 | return ( 300 |
301 |

Hello

302 |
303 | ); 304 | } 305 | ``` 306 | 307 |
308 | 309 |
310 | 311 | 📑 useConfirm 312 | 313 | ### useConfirm이란? 314 | 315 | 유저가 어떠한 이벤트를 발생 시 여부를 확인 하는 것 316 | 317 | ``` javascript 318 | const useConfirm = (message = "", onConfirm, onCancel) => { 319 | if (!onConfirm || typeof onConfirm !== "function") { 320 | return; 321 | } 322 | if (onCancel && typeof onCancel !== "function") { 323 | return; 324 | } 325 | const confirmAction = () => { 326 | if (confirm(message)) { 327 | onConfirm(); 328 | } else { 329 | onCancel(); 330 | } 331 | }; 332 | return confirmAction; 333 | }; 334 | 335 | function App() { 336 | const deleteWorld = () => console.log("Deleting the world..."); 337 | const abort = () => console.log("Aborted"); 338 | const confirmDelete = useConfirm("Are you sure", deleteWorld, abort); 339 | return ( 340 |
341 |

Hello

342 | 343 |
344 | ); 345 | } 346 | ``` 347 | 348 |
349 | 350 |
351 | 352 | 📑 usePreventLeave 353 | 354 | ### usePreventLeave이란? 355 | 356 | 유저가 변경사항이나 무엇이든간에 저장하지 않고 페이지를 벗어나길 원할 때 확인하는 것 357 | 358 | ``` javascript 359 | const usePreventLeave = () => { 360 | const listener = (event) => { 361 | event.preventDefault(); 362 | event.returnValue = ""; 363 | }; 364 | const enablePrevent = () => window.addEventListener("beforeunload", listener); 365 | const disaPrevent = () => 366 | window.removeEventListener("beforeunload", listener); 367 | return { enablePrevent, disaPrevent }; 368 | }; 369 | 370 | function App() { 371 | const { enablePrevent, disaPrevent } = usePreventLeave(); 372 | return ( 373 |
374 |

Hello

375 | 376 | 377 |
378 | ); 379 | } 380 | ``` 381 | 382 |
383 | 384 |
385 | 386 | 📑 useBeforeLeave 387 | 388 | ### useBeforeLeave이란? 389 | 390 | 유저가 페이지를 벗어나는 시점을 발견하고 함수를 실행 391 | 392 | ``` javascript 393 | const useBeforeLeave = (onBefore) => { 394 | if (typeof onBefore !== "function") { 395 | return; 396 | } 397 | const handle = (event) => { 398 | const { clientY } = event; 399 | if (clientY <= 0) { 400 | onBefore(); 401 | } 402 | }; 403 | useEffect(() => { 404 | document.addEventListener("mouseleave", handle); 405 | return () => document.removeEventListener("mouseleave", handle); 406 | }, []); 407 | }; 408 | 409 | function App() { 410 | const begForLife = () => console.log("Pls dont leave"); 411 | useBeforeLeave(begForLife); 412 | return ( 413 |
414 |

Hello

415 |
416 | ); 417 | } 418 | ``` 419 | 420 |
421 | 422 |
423 | 424 | 📑 useFadeIn 425 | 426 | ### useFadeIn이란? 427 | 428 | 어떤 element든 상관없이 애니메이션을 element 안으로 서서히 사라지게 만드는 것 429 | 430 | ``` javascript 431 | const useFadeIn = (duration = 1, delay = 0) => { 432 | if (typeof duration !== "number" || typeof delay !== "number") { 433 | return; 434 | } 435 | const element = useRef(); 436 | useEffect(() => { 437 | if (element.current) { 438 | const { current } = element; 439 | current.style.transition = `opacity ${duration}s ease-in-out ${delay}s`; 440 | current.style.opacity = 1; 441 | } 442 | }, []); 443 | return { ref: element, style: { opacity: 0 } }; 444 | }; 445 | 446 | function App() { 447 | const fadeInH1 = useFadeIn(1, 2); 448 | const fadeInP = useFadeIn(2, 3); 449 | return ( 450 |
451 |

Hello

452 |

453 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Laborum 454 | voluptatem debitis accusantium veritatis! Amet, minus consequatur eius 455 | ut dolorum expedita consectetur corrupti animi incidunt nihil iste illo, 456 | iure, voluptatem voluptates. 457 |

458 |
459 | ); 460 | } 461 | ``` 462 | 463 |
464 | 465 |
466 | 467 | 📑 useNetwork 468 | 469 | ### useNetwork이란? 470 | 471 | 현재 Online or Offline 상태인지를 감지하는 것 472 | 473 | ``` javascript 474 | const useNetwork = (onChange) => { 475 | const [status, setStatus] = useState(navigator.onLine); 476 | const handleChange = (event) => { 477 | if (typeof onChange === "function") { 478 | onChange(navigator.onLine); 479 | } 480 | setStatus(navigator.onLine); 481 | }; 482 | useEffect(() => { 483 | window.addEventListener("online", handleChange); 484 | window.addEventListener("offline", handleChange); 485 | return () => { 486 | window.removeEventListener("online", handleChange); 487 | window.removeEventListener("offline", handleChange); 488 | }; 489 | }, []); 490 | return status; 491 | }; 492 | 493 | function App() { 494 | const handleNetworkChange = (online) => { 495 | console.log(online ? "We just went online" : "We are offline"); 496 | }; 497 | const onLine = useNetwork(handleNetworkChange); 498 | return ( 499 |
500 |

Hello

501 |

{onLine ? "Online" : "Offline"}

502 |
503 | ); 504 | } 505 | ``` 506 | 507 |
508 | 509 |
510 | 511 | 📑 useScroll 512 | 513 | ### useScroll이란? 514 | 515 | 스크롤을 사용할 때를 감지해 알려주는 것 516 | 517 | ``` javascript 518 | const useScroll = () => { 519 | const [status, setStatus] = useState({ x: 0, y: 0 }); 520 | const onScroll = () => { 521 | setStatus({ x: window.scrollX, y: window.scrollY }); 522 | }; 523 | useEffect(() => { 524 | window.addEventListener("scroll", onScroll); 525 | return () => window.removeEventListener("scroll", onScroll); 526 | }, []); 527 | return status; 528 | }; 529 | 530 | function App() { 531 | const { y } = useScroll(); 532 | return ( 533 |
534 |

1000 ? "blue" : "red" }}> 535 | Hello 536 |

537 |
538 | ); 539 | } 540 | ``` 541 | 542 |
543 | 544 |
545 | 546 | 📑 useFullscreen 547 | 548 | ### useFullscreen이란? 549 | 550 | 어떤 element든 풀크스린으로 만들거나 일반 화면으로 돌아가게 하는 것 551 | 552 | ``` javascript 553 | const useFullscreen = (callback) => { 554 | const element = useRef(); 555 | const runCb = (isFull) => { 556 | if (callback && typeof callback === "function") { 557 | callback(isFull); 558 | } 559 | }; 560 | const triggerFull = () => { 561 | if (element.current) { 562 | if (element.current.requestFullscreen) { 563 | element.current.requestFullscreen(); 564 | } else if (element.current.mozRequestFullscreen) { 565 | element.current.mozRequestFullscreen(); 566 | } else if (element.current.webkitRequestFullscreen) { 567 | element.current.webkitRequestFullscreen(); 568 | } else if (element.current.msRequestFullscreen) { 569 | element.current.msRequestFullscreen(); 570 | } 571 | runCb(true); 572 | } 573 | }; 574 | const exitFull = () => { 575 | if (document.exitFullscreen) { 576 | document.exitFullscreen(); 577 | } else if (document.mozCancelFullScreen) { 578 | document.mozCancelFullScreen(); 579 | } else if (document.webkitExitFullscreen) { 580 | document.webkitExitFullscreen(); 581 | } else if (document.msExitFullscreen) { 582 | document.msExitFullscreen(); 583 | } 584 | runCb(false); 585 | }; 586 | return { element, triggerFull, exitFull }; 587 | }; 588 | 589 | function App() { 590 | const onFullS = (isFull) => { 591 | console.log(isFull ? "We are full" : "We are small"); 592 | }; 593 | const { element, triggerFull, exitFull } = useFullscreen(onFullS); 594 | return ( 595 |
596 |

Hello

597 |
598 | img 602 | 603 |
604 | 605 |
606 | ); 607 | } 608 | ``` 609 | 610 |
611 | 612 |
613 | 614 | 📑 useNotification 615 | 616 | ### useNotification이란? 617 | 618 | notification API를 사용할 때 유저에게 알람을 보내주는 것 619 | 620 | ``` javascript 621 | const useNotification = (title, options) => { 622 | if (!("Notification" in window)) { 623 | return; 624 | } 625 | const fireNotif = () => { 626 | if (Notification.permission !== "granted") { 627 | Notification.requestPermission().then((permission) => { 628 | if (permission === "granted") { 629 | new Notification(title, options); 630 | } else { 631 | return; 632 | } 633 | }); 634 | } else { 635 | new Notification(title, options); 636 | } 637 | }; 638 | return fireNotif; 639 | }; 640 | 641 | function App() { 642 | const triggerNotif = useNotification("Can I steal your kimchi?", { 643 | body: "I love kimchi dont you" 644 | }); 645 | return ( 646 |
647 |

Hello

648 | 649 |
650 | ); 651 | } 652 | ``` 653 | 654 | [Notification API](https://developer.mozilla.org/ko/docs/Web/API/notification) 655 | 656 |
657 | 658 |
659 | 660 | 📑 useAxios 661 | 662 | ### useAxios이란? 663 | 664 | HTTP requests client axios을 위한 wrapper 같은 것 665 | 666 | ``` javascript 667 | const useAxios = (options, axiosInstance = defaultAxios) => { 668 | const [state, setSate] = useState({ 669 | loading: true, 670 | error: null, 671 | data: null 672 | }); 673 | const [trigger, setTrigger] = useState(0); 674 | if (!options.url) { 675 | return; 676 | } 677 | const refetch = () => { 678 | setSate({ 679 | ...state, 680 | loading: true 681 | }); 682 | setTrigger(Date.now()); 683 | }; 684 | useEffect(() => { 685 | axiosInstance(options) 686 | .then((data) => { 687 | setSate({ 688 | ...state, 689 | loading: false, 690 | data 691 | }); 692 | }) 693 | .catch((error) => { 694 | setSate({ 695 | ...state, 696 | loading: false, 697 | error 698 | }); 699 | }); 700 | }, [trigger]); 701 | return { ...state, refetch }; 702 | }; 703 | 704 | function App() { 705 | const { loading, data, refetch } = useAxios({ 706 | url: 707 | "https://cors-anywhere.herokuapp.com/https://yts.am/api/v2/list_movies.json" 708 | }); 709 | console.log(loading, data, JSON.stringify(data), refetch); 710 | return ( 711 |
712 |

{data && data.status}

713 |

{loading && "Loading"}

714 | 715 |
716 | ); 717 | } 718 | ``` 719 | 720 |
721 | 722 | ## 📢 공부 진행 723 | 724 | - [X] useState 725 | - [X] useInput 726 | - [x] useTabs 727 | - [X] useTitle 728 | - [X] useClick 729 | - [X] useHover 730 | - [X] useConfirm - hook 사용 x 731 | - [X] usePreventLeave - hook 사용 x 732 | - [X] useBeforeLeave 733 | - [X] useFadeIn 734 | - [X] useNetwork 735 | - [X] useScroll 736 | - [X] useFullscreen 737 | - [X] useNotification 738 | - [X] useAxios 739 | -------------------------------------------------------------------------------- /useAxios.js: -------------------------------------------------------------------------------- 1 | import defaultAxios from "axios"; 2 | 3 | export const useAxios = (options, axiosInstance = defaultAxios) => { 4 | const [state, setSate] = useState({ 5 | loading: true, 6 | error: null, 7 | data: null 8 | }); 9 | const [trigger, setTrigger] = useState(0); 10 | if (!options.url) { 11 | return; 12 | } 13 | const refetch = () => { 14 | setSate({ 15 | ...state, 16 | loading: true 17 | }); 18 | setTrigger(Date.now()); 19 | }; 20 | useEffect(() => { 21 | axiosInstance(options) 22 | .then((data) => { 23 | setSate({ 24 | ...state, 25 | loading: false, 26 | data 27 | }); 28 | }) 29 | .catch((error) => { 30 | setSate({ 31 | ...state, 32 | loading: false, 33 | error 34 | }); 35 | }); 36 | }, [trigger]); 37 | return { ...state, refetch }; 38 | }; -------------------------------------------------------------------------------- /useBeforeLeave.js: -------------------------------------------------------------------------------- 1 | export const useBeforeLeave = (onBefore) => { 2 | if (typeof onBefore !== "function") { 3 | return; 4 | } 5 | const handle = (event) => { 6 | const { clientY } = event; 7 | if (clientY <= 0) { 8 | onBefore(); 9 | } 10 | }; 11 | useEffect(() => { 12 | document.addEventListener("mouseleave", handle); 13 | return () => document.removeEventListener("mouseleave", handle); 14 | }, []); 15 | }; -------------------------------------------------------------------------------- /useClick.js: -------------------------------------------------------------------------------- 1 | export const useClick = (onClick) => { 2 | if (typeof onclick !== "function") { 3 | return; 4 | } 5 | const element = useRef(); 6 | useEffect(() => { 7 | if (element.current) { 8 | element.current.addEventListener("click", onClick); 9 | } 10 | return () => { 11 | if (element.current) { 12 | element.current.removeEventListener("click", onClick); 13 | } 14 | }; 15 | }, []); 16 | return element; 17 | }; -------------------------------------------------------------------------------- /useConfirm.js: -------------------------------------------------------------------------------- 1 | export const useConfirm = (message = "", onConfirm, onCancel) => { 2 | if (!onConfirm || typeof onConfirm !== "function") { 3 | return; 4 | } 5 | if (onCancel && typeof onCancel !== "function") { 6 | return; 7 | } 8 | const confirmAction = () => { 9 | if (confirm(message)) { 10 | onConfirm(); 11 | } else { 12 | onCancel(); 13 | } 14 | }; 15 | return confirmAction; 16 | }; -------------------------------------------------------------------------------- /useFadeIn.js: -------------------------------------------------------------------------------- 1 | export const useFadeIn = (duration = 1, delay = 0) => { 2 | if (typeof duration !== "number" || typeof delay !== "number") { 3 | return; 4 | } 5 | const element = useRef(); 6 | useEffect(() => { 7 | if (element.current) { 8 | const { current } = element; 9 | current.style.transition = `opacity ${duration}s ease-in-out ${delay}s`; 10 | current.style.opacity = 1; 11 | } 12 | }, []); 13 | return { ref: element, style: { opacity: 0 } }; 14 | }; -------------------------------------------------------------------------------- /useFullscreen.js: -------------------------------------------------------------------------------- 1 | export const useFullscreen = (callback) => { 2 | const element = useRef(); 3 | const runCb = (isFull) => { 4 | if (callback && typeof callback === "function") { 5 | callback(isFull); 6 | } 7 | }; 8 | const triggerFull = () => { 9 | if (element.current) { 10 | if (element.current.requestFullscreen) { 11 | element.current.requestFullscreen(); 12 | } else if (element.current.mozRequestFullscreen) { 13 | element.current.mozRequestFullscreen(); 14 | } else if (element.current.webkitRequestFullscreen) { 15 | element.current.webkitRequestFullscreen(); 16 | } else if (element.current.msRequestFullscreen) { 17 | element.current.msRequestFullscreen(); 18 | } 19 | runCb(true); 20 | } 21 | }; 22 | const exitFull = () => { 23 | if (document.exitFullscreen) { 24 | document.exitFullscreen(); 25 | } else if (document.mozCancelFullScreen) { 26 | document.mozCancelFullScreen(); 27 | } else if (document.webkitExitFullscreen) { 28 | document.webkitExitFullscreen(); 29 | } else if (document.msExitFullscreen) { 30 | document.msExitFullscreen(); 31 | } 32 | runCb(false); 33 | }; 34 | return { element, triggerFull, exitFull }; 35 | }; -------------------------------------------------------------------------------- /useHover.js: -------------------------------------------------------------------------------- 1 | export const useHover = (onHover) => { 2 | if (typeof onHover !== "function") { 3 | return; 4 | } 5 | const element = useRef(); 6 | useEffect(() => { 7 | if (element.current) { 8 | element.current.addEventListener("mouseenter", onHover); 9 | } 10 | return () => { 11 | if (element.current) { 12 | element.current.removeEventListener("mouseenter", onHover); 13 | } 14 | }; 15 | }, []); 16 | return element; 17 | }; -------------------------------------------------------------------------------- /useInput.js: -------------------------------------------------------------------------------- 1 | export const useInput = (initialValue, validator) => { 2 | const [value, setValue] = useState(initialValue); 3 | const onChange = (event) => { 4 | const { 5 | target: { value } 6 | } = event; 7 | let willUpdate = true; 8 | if (typeof validator === "function") { 9 | willUpdate = validator(value); 10 | } 11 | if (willUpdate) { 12 | setValue(value); 13 | } 14 | }; 15 | return { value, onChange }; 16 | }; -------------------------------------------------------------------------------- /useNetwork.js: -------------------------------------------------------------------------------- 1 | export const useNetwork = (onChange) => { 2 | const [status, setStatus] = useState(navigator.onLine); 3 | const handleChange = (event) => { 4 | if (typeof onChange === "function") { 5 | onChange(navigator.onLine); 6 | } 7 | setStatus(navigator.onLine); 8 | }; 9 | useEffect(() => { 10 | window.addEventListener("online", handleChange); 11 | window.addEventListener("offline", handleChange); 12 | return () => { 13 | window.removeEventListener("online", handleChange); 14 | window.removeEventListener("offline", handleChange); 15 | }; 16 | }, []); 17 | return status; 18 | }; -------------------------------------------------------------------------------- /useNotification.js: -------------------------------------------------------------------------------- 1 | export const useNotification = (title, options) => { 2 | if (!("Notification" in window)) { 3 | return; 4 | } 5 | const fireNotif = () => { 6 | if (Notification.permission !== "granted") { 7 | Notification.requestPermission().then((permission) => { 8 | if (permission === "granted") { 9 | new Notification(title, options); 10 | } else { 11 | return; 12 | } 13 | }); 14 | } else { 15 | new Notification(title, options); 16 | } 17 | }; 18 | return fireNotif; 19 | }; -------------------------------------------------------------------------------- /usePreventLeave.js: -------------------------------------------------------------------------------- 1 | export const usePreventLeave = () => { 2 | const listener = (event) => { 3 | event.preventDefault(); 4 | event.returnValue = ""; 5 | }; 6 | const enablePrevent = () => window.addEventListener("beforeunload", listener); 7 | const disaPrevent = () => 8 | window.removeEventListener("beforeunload", listener); 9 | return { enablePrevent, disaPrevent }; 10 | }; -------------------------------------------------------------------------------- /useScroll.js: -------------------------------------------------------------------------------- 1 | export const useScroll = () => { 2 | const [status, setStatus] = useState({ x: 0, y: 0 }); 3 | const onScroll = () => { 4 | setStatus({ x: window.scrollX, y: window.scrollY }); 5 | }; 6 | useEffect(() => { 7 | window.addEventListener("scroll", onScroll); 8 | return () => window.removeEventListener("scroll", onScroll); 9 | }, []); 10 | return status; 11 | }; -------------------------------------------------------------------------------- /useTabs.js: -------------------------------------------------------------------------------- 1 | export const useTabs = (initialTab, allTabs) => { 2 | if (!allTabs || !Array.isArray(allTabs)) { 3 | return; 4 | } 5 | const [currentIndex, setCurrentIndex] = useState(initialTab); 6 | return { 7 | currnetItem: allTabs[currentIndex], 8 | changeItem: setCurrentIndex 9 | }; 10 | }; -------------------------------------------------------------------------------- /useTitle.js: -------------------------------------------------------------------------------- 1 | export const useTitle = (initialTitle) => { 2 | const [title, setTitle] = useState(initialTitle); 3 | const updateTitle = () => { 4 | const htmlTitle = document.querySelector("title"); 5 | htmlTitle.innerText = title; 6 | }; 7 | useEffect(updateTitle, [title]); 8 | return setTitle; 9 | }; --------------------------------------------------------------------------------