Jieunny์˜ ๋ธ”๋กœ๊ทธ

S2) Unit 9. [์‹ค์Šต] StateAirline Client ๋ณธ๋ฌธ

CodeStates/Training

S2) Unit 9. [์‹ค์Šต] StateAirline Client

Jieunny 2023. 2. 3. 16:58

๐Ÿ“ฃ  Ajax๋ฅผ ํ™œ์šฉํ•ด์„œ ํ•ญ๊ณตํŽธ ๊ฒ€์ƒ‰ ํŽ˜์ด์ง€ ๊ตฌํ˜„ํ•˜๊ธฐ

1๏ธโƒฃ ํ•ญ๊ณต๊ถŒ ๋ชฉ๋ก ํ•„ํ„ฐ๋ง

โœ”๏ธ Main ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ญ๊ณตํŽธ์„ ์กฐํšŒํ•œ๋‹ค.

    โžฐ  Main ์ปดํฌ๋„ŒํŠธ ๋‚ด `search` ํ•จ์ˆ˜๋Š” ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ๋‹ด๊ณ  ์žˆ๋Š” ์ƒํƒœ ๊ฐ์ฒด `condition`์„ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•œ๋‹ค.

โœ”๏ธ Search ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์ƒํƒœ ๋Œ์–ด์˜ฌ๋ฆฌ๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.

    โžฐ ๊ฒ€์ƒ‰ ํ™”๋ฉด์ด Search ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค.

    โžฐ Search ์ปดํฌ๋„ŒํŠธ์—๋Š” ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜ `search`๊ฐ€ `onSearch` props๋กœ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•œ๋‹ค.

    โžฐ ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜ `search`๋Š” Search ์ปดํฌ๋„ŒํŠธ์˜ `๊ฒ€์ƒ‰` ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค.

2๏ธโƒฃ AJAX ์š”์ฒญ

โœ”๏ธ Side Effect๋Š” useEffect์—์„œ ๋‹ค๋ค„์•ผ ํ•œ๋‹ค.

     โžฐ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค, FlightDataApi์˜ getFlight๋ฅผ ๊ฒ€์ƒ‰ ์กฐ๊ฑด๊ณผ ํ•จ๊ป˜ ์š”์ฒญํ•ด์•ผ ํ•œ๋‹ค.

     โžฐ getFlight์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„, flightList ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•œ๋‹ค.

     โžฐ ๋”์ด์ƒ, ์ปดํฌ๋„ŒํŠธ ๋‚ด ํ•„ํ„ฐ ํ•จ์ˆ˜ `filterByCondition`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

     โžฐ ๋”์ด์ƒ, ํ•˜๋“œ์ฝ”๋”ฉ๋œ flightList JSON์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. (์ดˆ๊ธฐ๊ฐ’์€ ๋นˆ ๋ฐฐ์—ด๋กœ ๋‘”๋‹ค.)

     โžฐ getFlight ์š”์ฒญ์ด ๋‹ค์†Œ ๋Š๋ฆฌ๋ฏ€๋กœ, ๋กœ๋”ฉ ์ƒํƒœ์— ๋”ฐ๋ผ LoadingIndicator ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ‘œ์‹œํ•ด์•ผ ํ•œ๋‹ค.

โœ”๏ธ FlightDataApi์—์„œ ๊ธฐ์กด ๊ตฌํ˜„ ๋Œ€์‹ , REST API๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๋ฐ”๊พผ๋‹ค.

     โžฐ ๊ฒ€์ƒ‰ ์กฐ๊ฑด๊ณผ ํ•จ๊ป˜ StatesAirline ์„œ๋ฒ„์—์„œ ํ•ญ๊ณตํŽธ ์ •๋ณด๋ฅผ ์š”์ฒญ(fetch)ํ•œ๋‹ค.

 

๐Ÿ’ก Main.js

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๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

export default function Main() {
  // ํ•ญ๊ณตํŽธ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ๋‹ด๊ณ  ์žˆ๋Š” ์ƒํƒœ
  const [condition, setCondition] = useState({
    departure: 'ICN',
  });
  const [flightList, setFlightList] = useState();
  const [loading, setLoading] = useState(false);	
  // ๋กœ๋”ฉ ํŽ˜์ด์ง€ ๋„์šฐ๊ธฐ ์œ„ํ•ด์„œ ๋กœ๋”ฉ ์ค‘์ธ์ง€ ์•„๋‹Œ์ง€ ๋‹ด๊ณ  ์žˆ๋Š” state

  // ์ฃผ์–ด์ง„ ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ์— ๋”ฐ๋ผ condition ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œ์ผœ์ฃผ๋Š” ํ•จ์ˆ˜
  const search = ({ departure, destination }) => {
    if (
      condition.departure !== departure ||
      condition.destination !== destination
    ) {
      console.log('condition ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ต๋‹ˆ๋‹ค');
      setCondition({departure, destination});
      // ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ condition ์„ ๋ณ€๊ฒฝ์‹œํ‚จ๋‹ค.
      // TODO: search ํ•จ์ˆ˜๊ฐ€ ์ „๋‹ฌ ๋ฐ›์•„์˜จ 'ํ•ญ๊ณตํŽธ ๊ฒ€์ƒ‰ ์กฐ๊ฑด' ์ธ์ž๋ฅผ condition ์ƒํƒœ์— ์ ์ ˆํ•˜๊ฒŒ ๋‹ด์•„๋ณด์„ธ์š”.
    }
  };

  global.search = search; // ์‹คํ–‰์—๋Š” ์ „ํ˜€ ์ง€์žฅ์ด ์—†์ง€๋งŒ, ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” ์ง€์šฐ์ง€ ๋งˆ์„ธ์š”!

  // TODO: Effeck Hook์„ ์ด์šฉํ•ด AJAX ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด์„ธ์š”.
  // TODO: ๋”๋ถˆ์–ด, ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์ง„ํ–‰๋จ์„ ๋ณด์—ฌ์ฃผ๋Š” ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ(<LoadingIndicator/>)๋ฅผ ์ œ๊ณตํ•ด๋ณด์„ธ์š”.
   useEffect(() => {  // ํ•ญ๊ณตํŽธ์„ ๊ฒ€์ƒ‰ํ–ˆ์„ ๋•Œ, ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. -> ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋™์•ˆ ๋กœ๋”ฉ์ฐฝ ๋„์šฐ๊ธฐ
    setLoading(true);	// ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋™์•ˆ ๋กœ๋”ฉ์ฐฝ์„ ๋„์šฐ๊ธฐ ์œ„ํ•ด์„œ true๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค.
    getFlight(condition).then((flights) => {
    // ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” getFlight ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. 
    //fetch๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ .then์œผ๋กœ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ flights์— ๋‹ด๋Š”๋‹ค.
      setFlightList(flights);
      setLoading(false);
    });
   }, [condition])
   // condition(๊ฒ€์ƒ‰) ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.

  // TODO: ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์ง€์‹œ์— ๋”ฐ๋ผ 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>
          {/* <FlightList list={flightList.filter(filterByCondition)} /> */}
          { loading ? <LoadingIndicator /> : <FlightList list={flightList} />}
        </div>

        <div className="debug-area">
          <Debug condition={condition} />
        </div>
        <img id="logo" alt="logo" src="codestates-logo.png" />
      </main>
    </div>
  );
}

 

๐Ÿ’ก Search.js

import { useState } from 'react';

function Search({onSearch}) {
  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});
    // props๋กœ ๋ฐ›์€ onSearch์—๋Š” search ํ•จ์ˆ˜๊ฐ€ ๋“ค์–ด์žˆ์œผ๋ฏ€๋กœ ๊ฒ€์ƒ‰ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด search ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
    // TODO: ์ง€์‹œ์— ๋”ฐ๋ผ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ props๋ฅผ ๋ฐ›์•„์„œ ์‹คํ–‰์‹œ์ผœ ๋ณด์„ธ์š”.
  };

  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;

 

๐Ÿ’ก FlightDataApi.js

import flightList from '../resource/flightList';
import fetch from 'node-fetch';

if (typeof window !== 'undefined') {
  localStorage.setItem('flight', JSON.stringify(flightList));
}

export function getFlight(filterBy = {}) {
  // HINT: ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๊ธฐ ์œ„ํ•ด, fetch๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ๊ตฌํ˜„์€ ์™„์ „ํžˆ ์‚ญ์ œ๋˜์–ด๋„ ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค.

  let query = '';

  if(filterBy.departure){
    query = query + `departure=${filterBy.departure}&`;
  }
  // Search.js ์—์„œ getFlight๋ฅผ ๋ถ€๋ฅผ ๋•Œ, departure์™€ destination ์ด ๋‹ด๊ธด ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ์ฃผ์—ˆ์œผ๋ฏ€๋กœ, 
  // filterBy์—๋Š” ๊ทธ ์ธ์ž๊ฐ€ ๋“ค์–ด์žˆ๋‹ค.
  // endpoint์— ์ „๋‹ฌํ•ด ์ค„ query๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.
  
  if(filterBy.destination){
    query = query + `destination=${filterBy.destination}`;
  }

  let endpoint = `http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight?${query}`;
  // fetch๋Š” url์„ ์ธ์ž๋กœ ๋ฐ›์œผ๋ฏ€๋กœ endpoint๋ฅผ ๋งŒ๋“ค์–ด์ค˜์„œ fetch์— ์ „๋‹ฌํ•œ๋‹ค.
    
  return fetch(endpoint).then((res) => res.json());
}

 

๐Ÿ“ฃ  ์‹œ์—ฐ ํ™”๋ฉด