import Head from 'next/head' import { useEffect, useState } from 'react' import { getFlight } from '../api/FlightDataApi' import FlightList from './component/FlightList' import LoadingIndicator from './component/LoadingIndicator' import Search from './component/Search' import Debug from './component/Debug' import json from '../resource/flightList' export default function Main() { const [condition, setCondition] = useState({ departure: 'ICN' }) const [flightList, setFlightList] = useState(json) const [isLoading, setIsLoading] = useState(false) useEffect(() => { setIsLoading(true) getFlight(condition) .then(filtered => setFlightList(filtered)) .then(() => setIsLoading(false)) } , [condition]) //검색조건이 바뀔때마다 // useEffect(() => { // getFlight(condition) // .then(filtered => { // setFlightList(filtered) // setIsLoading(false) // }) // }, [condition]) const search = ({ departure, destination }) => { if (condition.departure !== departure || condition.destination !== destination) { console.log('condition 상태를 변경시킵니다') setCondition({ departure: departure, destination: destination }) //Main 컴포넌트 내 `search` 함수는 검색 조건을 담고 있는 상태 객체 `condition`을 업데이트해야 합니다 } } global.search = search // 실행에는 전혀 지장이 없지만, 테스트를 위해 필요한 코드입니다. 이 코드는 지우지 마세요! return ( <div> <Head> <title>States Airline</title> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1> 여행가고 싶을 땐, States Airline </h1> <Search onSearch={search} /> <div className="table"> <div className="row-header"> <div className="col">출발</div> <div className="col">도착</div> <div className="col">출발 시각</div> <div className="col">도착 시각</div> <div className="col"></div> </div> {isLoading ? <LoadingIndicator /> : <FlightList list={flightList} />} </div> <div className="debug-area"> <Debug condition={condition} /> </div> </main> </div> ) }
[ 구조분해할당 ]
const search = ({departure, destination}) => console.log(departure, destination) search({departure: "ICN", destination: "DGU"}) // ICN, DGU //다른 방법의 구조분해할당도 있다. let departure = "ICN" let destination = "DGU" search({departure, destination}) // ICN, DGU let {departure, destination} = {departure: "ICN", destination: "DGU"} departure === "ICN" destination === "DGU"
1> Part 1
1. Main 컴포넌트 내 `search` 함수는 검색 조건을 담고 있는 상태 객체 `condition`을 업데이트해야 합니다.
상태 객체 condition의 초기형태 : {departure: 'ICN'}
상태객체 condition을 업데이트 하는 함수 : setCondition 함수
const search = ({ departure, destination }) => { if (condition.departure !== departure || condition.destination !== destination) { console.log('condition 상태를 변경시킵니다') setCondition({ departure: departure, destination: destination }) } } //onSearch({ departure: "ICN", destination: textDestination })
condition.departure, condition.destination이 적혀있는 것 보니 원래 condition객체에는 departure, destination이라는 key가 존재.
search의 인자로는 departure, destination의 값이 들어온다.
만약 condition.departure와 search의 인자로 들어온 departure가 다르거나,
condition.destination과 search의 인자로 들어온 destination이 다르면
console.log('condition 상태를 변경시킵니다')
그리고 condition 상태객체를 {departure: departure(search의 인자로 들어온 departure), destination: destination(search의 인자로 들어온 destination)}
2. Search 컴포넌트에는 상태 변경 함수 `search`가 `onSearch` props로 전달되어야 합니다.
--> Lifting state. 자식 컴포넌트에서 event로 search함수가 실행되면 부모 컴포넌트인 Main.js컴포넌트의 state가 변경됨.
<Search onSearch={search} />
3. 상태 변경 함수 `search`는 Search 컴포넌트의 `검색` 버튼 클릭 시 실행되어야 합니다.
즉, 검색버튼 이벤트 핸들러로 search함수 실행되어야 한다.
import { useState, useEffect } from 'react' import { getFlight } from '../../api/FlightDataApi' function Search({ onSearch }) { //props로 onSearch(search함수) 내려받음 const [textDestination, setTextDestination] = useState('') const handleChange = (e) => { setTextDestination(e.target.value.toUpperCase()) } const handleKeyPress = (e) => { if (e.type === 'keypress' && e.code === 'Enter') { handleSearchClick() } } const handleSearchClick = () => { console.log('검색 버튼을 누르거나, 엔터를 치면 search 함수가 실행됩니다') onSearch({ departure: "ICN", destination: textDestination }) } return <fieldset> <legend>공항 코드를 입력하고, 검색하세요</legend> <span>출발지</span> <input id="input-departure" type="text" disabled value="ICN"></input> <span>도착지</span> <input id="input-destination" type="text" value={textDestination} onChange={handleChange} placeholder="CJU, BKK, PUS 중 하나를 입력하세요" onKeyPress={handleKeyPress} /> <button id="search-btn" onClick={handleSearchClick}>검색</button> </fieldset> } export default Search
<button id="search-btn" onClick={handleSearchClick}>검색</button>
const handleSearchClick = () => { console.log('검색 버튼을 누르거나, 엔터를 치면 search 함수가 실행됩니다') onSearch({ departure: "ICN", destination: textDestination }) }
button을 클릭했을 때(onClick), handleSearchClick함수가 실행된다.
event handler로 등록된 handleSearchClick의 함수를 보면, onSearch함수가 실행되고, 인자로는 아까 search함수에서 보듯이 departure, destination이 들어가고 검색조건을 변경하는 것.
ICN은 원래 고정되어있기때문에 onSearch({departure: "ICN", }), destination은 아래 코드를 보면 textDestination이라는 것을 유추해 낼 수 있다.
<input id="input-destination" type="text" value={textDestination} onChange={handleChange} placeholder="CJU, BKK, PUS 중 하나를 입력하세요" onKeyPress={handleKeyPress} />
2> Part 2 : AJAX 요청
1. 검색 조건이 바뀔 때마다, FlightDataApi의 getFlight를 검색 조건과 함께 요청해야 합니다.
먼저 hook은 최상위(Main.js컴포넌트)에서만 호출 가능.
1) 검색조건이 바뀔때마다 : useEffect에 종속성배열에 검색조건 상태객체인 condition이 들어간다.
- useEffect(() => {}, [condition])
2) FlightDataApi의 getFlight를 검색 조건과 함께 요청
- getFlight 함수를 보면 인자로 '검색조건(condition 객체)'가 들어간다는 것을 알 수 있다.
export function getFlight(filterBy = {}) { let queryString = ''; if (filterBy.departure) { queryString = queryString + `departure=${filterBy.departure}&` } if (filterBy.destination) { queryString = queryString + `destination=${filterBy.destination}` } return fetch(`http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight?${queryString}`) .then(res => res.json()) }
그래서,
useEffect(() => getFlight(condition) , [condition])
2. getFlight의 결과를 받아, flightList 상태를 업데이트해야 합니다.
flightList상태를 업데이트 하는 함수 : setFlightList
현재 flightList의 초기 상태는 아래와 같은 형태로 되어있다.
const flightList = [ { uuid: 'af6fa55c-da65-47dd-af23-578fdba40bed', departure: 'ICN', destination: 'CJU', departure_times: '2021-12-02T12:00:00', arrival_times: '2021-12-03T12:00:00' }, ]
getFlight의 결과 : getFlight(condition) === return fetch(URL).then(res => res.json())
즉, getFlight(condition)은 Promise 객체인 상태이다.
res.json()값을 받아오려면 또 then을 사용해야 한다.
useEffect(() => getFlight(condition).then(flight => setFlightList(flight)) , [condition])
여기서 flight은 아래와 같이 되어있다.
[{ "uuid": "af6fa55c-da65-47dd-af23-578fdba40bed", "departure": "ICN", "destination": "CJU", "departure_times": "2021-12-02T12:00:00", "arrival_times": "2021-12-03T12:00:00" }, ]
3. getFlight 요청이 다소 느리므로, 로딩 상태에 따라 LoadingIndicator 컴포넌트를 표시해야 합니다.
< LoadingIndicator 컴포넌트 >
function LoadingIndicator() { return <img className="loading-indicator" alt="now loading..." src="loading.gif" style={{ margin: '1rem' }} /> }
getFlight 요청결과 아직 오지 않았으면 로딩되어야 하고, getFlight요청이 오면 <FlightList />컴포넌트가 구현되어야.
1) 먼저 상태로 isLoading을 선언. 초기값을 false로 주었다.
우선은 isLoading이 false이기 때문에 <FlightList list={flightList} /> 컴포넌트가 구현된다.
const [isLoading, setIsLoading] = useState(false) <div className="table"> <div className="row-header"> <div className="col">출발</div> <div className="col">도착</div> <div className="col">출발 시각</div> <div className="col">도착 시각</div> <div className="col"></div> </div> {isLoading ? <LoadingIndicator /> : <FlightList list={flightList} />} </div>
2) condition이 바뀌면 setIsLoading(true)로 바뀌면서 <LoadingIndicator />함수 구현
getFlight(condition) 요청하여 결과 받으면 setIsLoading(false)로 변경
//방법 1 useEffect(() => { setIsLoading(true) getFlight(condition) .then(filtered => setFlightList(filtered)) .then(() => setIsLoading(false)) } , [condition]) //방법 2 useEffect(() => { setIsLoading(true) getFlight(condition) .then(filtered => { setFlightList(filtered) setIsLoading(false) }) }, [condition])
'React💃' 카테고리의 다른 글
[React] UseEffect가 두번 실행되는 이유 (0) | 2024.01.16 |
---|---|
Effect Hook (2) - 조건부 실행(dependency array), 명언제조기 filter (0) | 2021.09.25 |
React에서의 useEffect, Life Cycle, 순수함수 (0) | 2021.09.25 |
React Modal창 구현 (0) | 2021.09.14 |