From d69526d87bec49581efaa19b7b2e5f77dcb7b17a Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 09:48:17 +0900 Subject: [PATCH 01/12] make useInput custom hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 숫자만 입력가능한 input type text custom hook 생성 --- src/App.js | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/App.js b/src/App.js index 3784575..069ee4d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,22 @@ -import logo from './logo.svg'; -import './App.css'; +import {useState} from 'react'; + +const useInput = (initialValue,validator)=>{ + const [value,setValue] = useState(initialValue); + const onChange = ({target:{value}}) =>{ + if(typeof validator ==='function' && validator(value)){ + setValue(value) + }else{ + alert("must be numeric") + } + } + return {value,onChange} +} function App() { + const myValidator = (str) => !isNaN(str) + const name = useInput(1,myValidator) return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ ); } From a29200bd37e68658657a0a4f2d32c55db65efd5e Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 11:34:12 +0900 Subject: [PATCH 02/12] make useTabs custom hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 누르는 button에 따라 content div 가 결정되는 기능 --- src/App.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/App.js b/src/App.js index 069ee4d..7971421 100644 --- a/src/App.js +++ b/src/App.js @@ -1,23 +1,32 @@ import {useState} from 'react'; -const useInput = (initialValue,validator)=>{ - const [value,setValue] = useState(initialValue); - const onChange = ({target:{value}}) =>{ - if(typeof validator ==='function' && validator(value)){ - setValue(value) - }else{ - alert("must be numeric") - } +const useTabs = (initialTab,allTabs)=>{ + const [currentIndex,setCurrentIndex] = useState(initialTab); + if(!allTabs || !Array.isArray(allTabs)){ return; } + const changeItem=(index)=>{ + setCurrentIndex(index); } - return {value,onChange} + return {currentItem: allTabs[currentIndex].content,changeItem} } +const contents = [ + { + tab:'section1', + content:'asdf' + }, + { + tab:`section2`, + content:'zxcv' + } +] + function App() { - const myValidator = (str) => !isNaN(str) - const name = useInput(1,myValidator) + const {currentItem,changeItem} = useTabs(0,contents) return ( - - ); +
+ {contents.map((content,index)=>)} +

{currentItem}

+
+ ) } - export default App; From bb81c742d7599cbde7d5f3ebf69cbd743f3a40fe Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 12:28:30 +0900 Subject: [PATCH 03/12] make setTitle customehook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useEffect이용하여 일정 시간 후 loading->done browser title 설정 - setTitle을 반환해서 이 반환된 함수가 실행될때마다 useEffect가 실행되도록 함 --- src/App.js | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/App.js b/src/App.js index 7971421..03619b1 100644 --- a/src/App.js +++ b/src/App.js @@ -1,32 +1,23 @@ -import {useState} from 'react'; +import {useState,useEffect} from 'react'; -const useTabs = (initialTab,allTabs)=>{ - const [currentIndex,setCurrentIndex] = useState(initialTab); - if(!allTabs || !Array.isArray(allTabs)){ return; } - const changeItem=(index)=>{ - setCurrentIndex(index); +const useTitle = (initialTitle) => { + const [title,setTitle] = useState(initialTitle); + const updateTitle = () =>{ + const htmlTitle = document.querySelector('title'); + htmlTitle.innerText = title; } - return {currentItem: allTabs[currentIndex].content,changeItem} + useEffect(updateTitle,[title]); + return setTitle; } -const contents = [ - { - tab:'section1', - content:'asdf' - }, - { - tab:`section2`, - content:'zxcv' - } -] - function App() { - const {currentItem,changeItem} = useTabs(0,contents) + const titleUpdater = useTitle("Loading..."); + setTimeout(()=>{ + titleUpdater("done"); + },1000) + return ( -
- {contents.map((content,index)=>)} -

{currentItem}

-
+
asdf
) } export default App; From 60b79ca19a894000f0999b6eb496d348cc0b2a78 Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 14:35:45 +0900 Subject: [PATCH 04/12] make useClick customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onClick 이벤트가 없는 친구한테 onClick 이벤트 만들어주고 등록 - eventListener등록 후에는 등록된 이벤트 삭제 해줘야함 - clear함수는 wilUnmount단에서 삭제됨 --- src/App.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/App.js b/src/App.js index 03619b1..a5db531 100644 --- a/src/App.js +++ b/src/App.js @@ -1,23 +1,24 @@ -import {useState,useEffect} from 'react'; +import {useState,useEffect, useRef} from 'react'; -const useTitle = (initialTitle) => { - const [title,setTitle] = useState(initialTitle); - const updateTitle = () =>{ - const htmlTitle = document.querySelector('title'); - htmlTitle.innerText = title; - } - useEffect(updateTitle,[title]); - return setTitle; +const useClick = (onClick) =>{ + const element = useRef(); + useEffect(()=>{ + if(element.current){ + element.current.addEventListener('click',onClick) + } + }) + return element; + } function App() { - const titleUpdater = useTitle("Loading..."); - setTimeout(()=>{ - titleUpdater("done"); - },1000) + const sayHello = () => console.log('say Hello') + const title = useClick(sayHello) return ( -
asdf
+
+

Hi

+
) } export default App; From c43365373dd92ab4d94ddda3028b0ee62631ef32 Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 14:49:30 +0900 Subject: [PATCH 05/12] make useConfirm customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - confirm 창 생성 확인/취소 시 상황에 맞는 callback 함수 실행 --- src/App.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/App.js b/src/App.js index a5db531..de9a2d4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,23 +1,30 @@ -import {useState,useEffect, useRef} from 'react'; +import React,{useState,useEffect, useRef} from 'react'; +import ReactDOM from 'react-dom' -const useClick = (onClick) =>{ - const element = useRef(); - useEffect(()=>{ - if(element.current){ - element.current.addEventListener('click',onClick) + +const useConfirm = (message="", onConfirm, onReject) =>{ + if(typeof onConfirm !=='function'){ + return; + } + const confirmAction = () =>{ + // eslint-disable-next-line no-restricted-globals + if(confirm(message)){ + onConfirm(); + }else{ + onReject(); } - }) - return element; - + } + return confirmAction; } function App() { - const sayHello = () => console.log('say Hello') - const title = useClick(sayHello) - + const deleteWorld = () => console.log('deleting the world') + + const abort = () => console.log('Aborted'); + const confirmDelete = useConfirm("Are you sure?",deleteWorld,abort); return (
-

Hi

+
) } From 6ae420c6756f98224acfa5ec6ef02ca847a455f7 Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 15:11:46 +0900 Subject: [PATCH 06/12] make usePreventLeave customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 페이지 이동 전에 정말 이동할지 confirm창 생성 - beforeunload, event.preventDefault, event.returnValue의 조합으로 페이지 이동을 멈춘다 --- src/App.js | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/App.js b/src/App.js index de9a2d4..2e96efb 100644 --- a/src/App.js +++ b/src/App.js @@ -2,29 +2,23 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' -const useConfirm = (message="", onConfirm, onReject) =>{ - if(typeof onConfirm !=='function'){ - return; +const usePreventLeave = (message="",isEnabled=false) =>{ + const listener = (e)=>{ + e.preventDefault(); + e.returnValue ="" // 이것이 페이지 이동을 막아준다. } - const confirmAction = () =>{ - // eslint-disable-next-line no-restricted-globals - if(confirm(message)){ - onConfirm(); - }else{ - onReject(); - } - } - return confirmAction; + const enablePrevent = () => window.addEventListener('beforeunload',listener) + const disablePrevent = () => window.removeEventListener('beforeunload',listener) + + return {enablePrevent,disablePrevent}; } function App() { - const deleteWorld = () => console.log('deleting the world') - - const abort = () => console.log('Aborted'); - const confirmDelete = useConfirm("Are you sure?",deleteWorld,abort); + const {enablePrevent:protect,disablePrevent:unprotect} = usePreventLeave(); return (
- + +
) } From 67e5db2c1df026329af6d6f1887b79e069fa91df Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 15:55:55 +0900 Subject: [PATCH 07/12] make useFadeIn customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - duration 만큼 opacity: 0->1 로 변화하는 customhook - css만으로 구현 --- src/App.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/App.js b/src/App.js index 2e96efb..cbeba48 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,25 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' - -const usePreventLeave = (message="",isEnabled=false) =>{ - const listener = (e)=>{ - e.preventDefault(); - e.returnValue ="" // 이것이 페이지 이동을 막아준다. - } - const enablePrevent = () => window.addEventListener('beforeunload',listener) - const disablePrevent = () => window.removeEventListener('beforeunload',listener) +const useFadeIn = (duration) =>{ + const element = useRef(); + useEffect(()=>{ + if(element.current){ + element.current.style.transition = `opacity ease ${duration}s` + element.current.style.opacity = 1 + } + },[]) - return {enablePrevent,disablePrevent}; + return {ref:element,style:{opacity:0}}; } function App() { - const {enablePrevent:protect,disablePrevent:unprotect} = usePreventLeave(); + const fadeInH1 = useFadeIn(3); + const fadeInP = useFadeIn(10); return ( -
- - +
+

asdifjoiasdjgioasjg

+

asdfasdf

) } From 801b5ce287134e4ca56762468a8edd1ea5eba5bb Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 16:24:44 +0900 Subject: [PATCH 08/12] make useScroll customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scroll 이동시마다 특정행동 수행 --- src/App.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/App.js b/src/App.js index cbeba48..d3426d5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,31 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' -const useFadeIn = (duration) =>{ - const element = useRef(); - useEffect(()=>{ - if(element.current){ - element.current.style.transition = `opacity ease ${duration}s` - element.current.style.opacity = 1 - } - },[]) - - return {ref:element,style:{opacity:0}}; +const useScroll = () =>{ + const [state,setState] = useState({ + x:0, + y:0 + }); + const onScroll = ({target}) =>{ + const {scrollTop,scrollLeft} = target; + setState({ + x:scrollTop, + y:scrollLeft + }) + } + return {state,onScroll} } function App() { - const fadeInH1 = useFadeIn(3); - const fadeInP = useFadeIn(10); + const {state,onScroll} = useScroll(); return ( -
-

asdifjoiasdjgioasjg

-

asdfasdf

+
+
+

asdf22222222222222222222222222222

+

asdf2222222222222222222222222222

+

asdf22222222222222222222222222

+
+
x:{state.x} y:{state.y}
) } From bc351c4d68d38a38a594dbd14a0fcf5ad617b086 Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 17:21:12 +0900 Subject: [PATCH 09/12] make useAxios customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - loading, error, data, refetch 반환 --- package.json | 1 + src/App.js | 27 ++++++--------------------- src/useAxios.js | 36 ++++++++++++++++++++++++++++++++++++ yarn.lock | 12 ++++++++++++ 4 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 src/useAxios.js diff --git a/package.json b/package.json index 910845e..433b6af 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "axios": "^0.24.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", diff --git a/src/App.js b/src/App.js index d3426d5..d053176 100644 --- a/src/App.js +++ b/src/App.js @@ -1,31 +1,16 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' +import useAxios from './useAxios.js' -const useScroll = () =>{ - const [state,setState] = useState({ - x:0, - y:0 - }); - const onScroll = ({target}) =>{ - const {scrollTop,scrollLeft} = target; - setState({ - x:scrollTop, - y:scrollLeft - }) - } - return {state,onScroll} -} function App() { - const {state,onScroll} = useScroll(); + const {loading,error,data,refetch} = useAxios({method:'get',url:`https://yts.am/api/v2/list_movies.json`}); + console.log(`Loading: ${loading}, error:${error}, data:${data}`); return (
-
-

asdf22222222222222222222222222222

-

asdf2222222222222222222222222222

-

asdf22222222222222222222222222

-
-
x:{state.x} y:{state.y}
+ {loading &&
Loading
} + {error &&
{error}
} + {data &&
{JSON.stringify(data)}
}
) } diff --git a/src/useAxios.js b/src/useAxios.js new file mode 100644 index 0000000..745fd49 --- /dev/null +++ b/src/useAxios.js @@ -0,0 +1,36 @@ +import defaultAxios from 'axios'; +import {useEffect, useState} from 'react' + +const useAxios = (opts, axiosInstance = defaultAxios) =>{ + const [state,setState] = useState({ + loading:true, + error:null, + data:null, + }); + const [trigger,setTrigger] = useState() + if(!opts.url){ + return; + } + const refetch = () =>{ + setState({ + ...state, + loading:false + }) + setTrigger(Date.now()); + } + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(()=>{ + axiosInstance.request({...opts}).then((res)=>{ + console.log(res); + setState({ + loading:false, + error: res.error , + data:res.data + }) + }) + },[trigger]) + return {...state, refetch}; + +} + +export default useAxios; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5c7137e..d4582b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2522,6 +2522,13 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224" integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -5032,6 +5039,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== +follow-redirects@^1.14.4: + version "1.14.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" + integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" From 1d00ab111c6d12304bcb5c4bd27377e4a4afbd35 Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Tue, 2 Nov 2021 20:49:10 +0900 Subject: [PATCH 10/12] make modal component,useModal customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - React.Fragment - 버튼 클릭시 Modal 생성 --- src/App.css | 76 +++++++++++++++++++++++++++++++++---------------- src/App.js | 15 ++++++---- src/Modal.js | 22 ++++++++++++++ src/useAxios.js | 36 ----------------------- src/useModal.js | 12 ++++++++ 5 files changed, 95 insertions(+), 66 deletions(-) create mode 100644 src/Modal.js delete mode 100644 src/useAxios.js create mode 100644 src/useModal.js diff --git a/src/App.css b/src/App.css index 74b5e05..30026f2 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,66 @@ .App { text-align: center; + padding-top: 2rem; } -.App-logo { - height: 40vmin; - pointer-events: none; +.modal-overlay { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; + opacity: .5; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +.modal-wrapper { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0; } -.App-header { - background-color: #282c34; - min-height: 100vh; +.modal { + z-index: 100; + background: white; + position: relative; + margin: 1.75rem auto; + border-radius: 3px; + max-width: 500px; + padding: 2rem; +} + +.modal-header { display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; + justify-content: flex-end; } -.App-link { - color: #61dafb; +.modal-close-button { + font-size: 1.4rem; + font-weight: 700; + line-height: 1; + color: #000; + opacity: .3; + cursor: pointer; + border: none; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +button { + font-size: .9rem; + font-weight: 700; + border: none; + border-radius: 3px; + padding: .3rem 1rem; + margin-left: .5rem; } + +.button-default { + background: #247BA0; + color: #fff; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index d053176..c69e9c9 100644 --- a/src/App.js +++ b/src/App.js @@ -1,16 +1,19 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' -import useAxios from './useAxios.js' +import Modal from './Modal.js' +import useModal from './useModal.js' +import './App.css'; function App() { - const {loading,error,data,refetch} = useAxios({method:'get',url:`https://yts.am/api/v2/list_movies.json`}); - console.log(`Loading: ${loading}, error:${error}, data:${data}`); + const {isShowing, toggle} = useModal(); return (
- {loading &&
Loading
} - {error &&
{error}
} - {data &&
{JSON.stringify(data)}
} + +
) } diff --git a/src/Modal.js b/src/Modal.js new file mode 100644 index 0000000..b155f46 --- /dev/null +++ b/src/Modal.js @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +const Modal = ({ isShowing, hide }) => isShowing ? ReactDOM.createPortal( + +
+
+
+
+ +
+

+ Hello, I'm a modal. +

+
+
+ , document.body +) : null; + +export default Modal; \ No newline at end of file diff --git a/src/useAxios.js b/src/useAxios.js deleted file mode 100644 index 745fd49..0000000 --- a/src/useAxios.js +++ /dev/null @@ -1,36 +0,0 @@ -import defaultAxios from 'axios'; -import {useEffect, useState} from 'react' - -const useAxios = (opts, axiosInstance = defaultAxios) =>{ - const [state,setState] = useState({ - loading:true, - error:null, - data:null, - }); - const [trigger,setTrigger] = useState() - if(!opts.url){ - return; - } - const refetch = () =>{ - setState({ - ...state, - loading:false - }) - setTrigger(Date.now()); - } - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(()=>{ - axiosInstance.request({...opts}).then((res)=>{ - console.log(res); - setState({ - loading:false, - error: res.error , - data:res.data - }) - }) - },[trigger]) - return {...state, refetch}; - -} - -export default useAxios; \ No newline at end of file diff --git a/src/useModal.js b/src/useModal.js new file mode 100644 index 0000000..d4c3cdd --- /dev/null +++ b/src/useModal.js @@ -0,0 +1,12 @@ +import React,{useState,useEffect, useRef} from 'react'; + +const useModal = () =>{ + const [isShowing, setIsShowing] = useState(false); + + function toggle(){ + setIsShowing(!isShowing); + } + return {isShowing,toggle}; +} + +export default useModal; \ No newline at end of file From 064df7617dfa5ea9b62d73e0cb11279159c7f40a Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Wed, 3 Nov 2021 22:03:07 +0900 Subject: [PATCH 11/12] React.memo and useMemo and useCallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - React.memo : 상위 컴포넌트가 갱신되어도 하위 컴포넌트의 props나 state에 변화가 없어 rerender가 필요없을때 rerender 안함 - useMemo : 2번째 parameter로 넘긴 변수가 변하지 않는 이상 rerender되어도 computed 변수를 다시 계산하지 않는다 - useCallback : 2번째 paramter로 내부에서 사용하는 state, props를 넣어준다. --- src/App.js | 66 +++++++++++++++++++++++++++++++++--------- src/Modal.js | 22 -------------- src/index.js | 6 ---- src/reportWebVitals.js | 13 --------- src/useModal.js | 12 -------- 5 files changed, 53 insertions(+), 66 deletions(-) delete mode 100644 src/Modal.js delete mode 100644 src/reportWebVitals.js delete mode 100644 src/useModal.js diff --git a/src/App.js b/src/App.js index c69e9c9..6b2df1e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,20 +1,60 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' -import Modal from './Modal.js' -import useModal from './useModal.js' import './App.css'; -function App() { - const {isShowing, toggle} = useModal(); +import Info from "./Info"; + +const App = () => { + const [color, setColor] = useState(""); + const [movie, setMovie] = useState(""); + + const onChangeHandler = e => { + if (e.target.id === "color") setColor(e.target.value); + else setMovie(e.target.value); + }; + return ( -
- - +
+
+ +
+
+ What is your favorite movie among these ? + + + +
+
- ) -} -export default App; + ); +}; + +export default App; \ No newline at end of file diff --git a/src/Modal.js b/src/Modal.js deleted file mode 100644 index b155f46..0000000 --- a/src/Modal.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -const Modal = ({ isShowing, hide }) => isShowing ? ReactDOM.createPortal( - -
-
-
-
- -
-

- Hello, I'm a modal. -

-
-
- , document.body -) : null; - -export default Modal; \ No newline at end of file diff --git a/src/index.js b/src/index.js index ef2edf8..6832e78 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; -import reportWebVitals from './reportWebVitals'; ReactDOM.render( @@ -10,8 +9,3 @@ ReactDOM.render( , document.getElementById('root') ); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3a..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/useModal.js b/src/useModal.js deleted file mode 100644 index d4c3cdd..0000000 --- a/src/useModal.js +++ /dev/null @@ -1,12 +0,0 @@ -import React,{useState,useEffect, useRef} from 'react'; - -const useModal = () =>{ - const [isShowing, setIsShowing] = useState(false); - - function toggle(){ - setIsShowing(!isShowing); - } - return {isShowing,toggle}; -} - -export default useModal; \ No newline at end of file From 3b55c4a5d74839a422cbb134a8205e33077461fe Mon Sep 17 00:00:00 2001 From: korkt-kim Date: Sat, 6 Nov 2021 12:32:10 +0900 Subject: [PATCH 12/12] make useAsync customhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useReducer를 이용한 customhook 생성 - api fetching상태, success상태, error상태 구분하는 state반환 - api fetch method 반환 --- src/App.js | 56 ++++++++----------------------------------- src/App.test.js | 8 ------- src/apis/myAPI.js | 17 +++++++++++++ src/hooks/useAsync.js | 38 +++++++++++++++++++++++++++++ src/logo.svg | 1 - src/setupTests.js | 5 ---- 6 files changed, 65 insertions(+), 60 deletions(-) delete mode 100644 src/App.test.js create mode 100644 src/apis/myAPI.js create mode 100644 src/hooks/useAsync.js delete mode 100644 src/logo.svg delete mode 100644 src/setupTests.js diff --git a/src/App.js b/src/App.js index 6b2df1e..23cffe1 100644 --- a/src/App.js +++ b/src/App.js @@ -1,58 +1,22 @@ import React,{useState,useEffect, useRef} from 'react'; import ReactDOM from 'react-dom' +import myAPI from './apis/myAPI'; import './App.css'; +import useAsync from './hooks/useAsync'; -import Info from "./Info"; - const App = () => { - const [color, setColor] = useState(""); - const [movie, setMovie] = useState(""); - - const onChangeHandler = e => { - if (e.target.id === "color") setColor(e.target.value); - else setMovie(e.target.value); - }; + const [{data:employees,loading,error},fetchEmployee] = useAsync(myAPI); + + useEffect(()=>{ + fetchEmployee(); + },[]) return (
-
- -
-
- What is your favorite movie among these ? - - - -
- + {loading ?

Loading

:''} + {error ?

error!

: ''} + {employees?
    {employees.map(employee=>
  • {employee.name}
    {employee.email}
  • )}
: ''}
); }; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/apis/myAPI.js b/src/apis/myAPI.js new file mode 100644 index 0000000..f604897 --- /dev/null +++ b/src/apis/myAPI.js @@ -0,0 +1,17 @@ +const myAPI = ()=>{ + return new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve([{ + id:1, + name:'SAM', + email:'korkt1.kim@samsung.com' + },{ + id:2, + name:'SUNG', + email:'korkt2.kim@samsung.com' + }]) + },2000) + }) +} + +export default myAPI; \ No newline at end of file diff --git a/src/hooks/useAsync.js b/src/hooks/useAsync.js new file mode 100644 index 0000000..708ac5d --- /dev/null +++ b/src/hooks/useAsync.js @@ -0,0 +1,38 @@ +import React,{useReducer} from 'react'; + +const initialState ={ + data:null, + loading:false, + error: null, +} + +const reducer = (state,action) =>{ + switch (action.type){ + case 'LOADING': + return {...state,loading:true,error:null,data:null}; + case 'SUCCESS': + return {...state,data:action.data,loading:false,error:null}; + case 'ERROR' : + return {...state,data:null,loading:false,error:action.error} + default: + return {...state} + } +} + + +const useAsync = (callback) =>{ + const [state,dispatch] = useReducer(reducer,initialState) + const fetchData = async () =>{ + dispatch({type:'LOADING'}); + try{ + const data = await callback(); + dispatch({type:'SUCCESS',data}); + }catch(e){ + dispatch({type:'ERROR',error:e}); + } + + } + return [state,fetchData]; +} + +export default useAsync \ No newline at end of file diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom';