kakao map api
를 통해 지도를 렌더링하는 함수는 useEffect
를 통해 단 한번만 실행되도록 하였습니다.context
에 저장하여 리스트, 검색 바에서 지도 정보가 필요할 시 바로 context
를 사용해 바로 접근할 수 있도록 하였습니다.// 지도의 좌표 범위를 보내고, 범위 내의 매물을
// Context에 저장하는 fetch 함수
const sendBoundGetItem = () => {
// 거래 종류(전세, 월세) 필터를 query에 담는 조건문
if (
Object.entries(tradeTypeFilter).filter(el => el[1] === true).length !== 0
) {
tradeTypeQuery = Object.entries(tradeTypeFilter)
.filter(el => el[1] === true)
.map(el => el[0])
.toString();
}
fetch(`${BASE_URL}/estates?tradeType=${tradeTypeQuery}`, {
method: 'GET',
headers: {
'Content-type': 'application/json',
LatLng: `${mapBounds.ha},${mapBounds.oa},${mapBounds.qa},${mapBounds.pa}`,
},
})
.then(res => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res.json();
})
.catch(err => {
// 응답 에러 시 context의 매물 정보를 빈 배열로 전환
console.log(err.message)
RealEstateDispatch({ type: 'GET_REAL_ESTATE', realEstate: [] });
})
.then(data => {
// 해당 범위 내의 존재하는 매물이 없다면 context에 빈 배열로 저장
// fetch로 받은 매물에서 방종류(원룸, 빌라, 오피스텔, 아파트)를 필터링하는 로직
if (
Object.values(RealEstate.roomTypeFilter).filter(
filter => filter.isOn === true
).length < 4
) {
const filteredData = data.clusters.filter(estate =>
Object.values(RealEstate.roomTypeFilter).find(
filter => filter.roomType === estate.category_type && filter.isOn
)
);
RealEstateDispatch({
type: 'GET_REAL_ESTATE',
realEstate: filteredData,
});
return;
} else {
RealEstateDispatch({
type: 'GET_REAL_ESTATE',
realEstate: data.clusters,
});
return;
}
});
};
...
// 지도 범위, 필터, 거래 타입이 업데이트 될 때마다 fetch함수가 실행,
// context.js에 범위 내 매물 저장
useEffect(() => {
sendBoundGetItem();
}, [mapBounds, roomTypeFilter, tradeTypeFilter]);
context
에 저장된 전체 매물 중에서 클러스터에 포함된 매물과 같은 좌표를 갖는 매물을 필터링하는 이벤트 핸들러.context
의 selected
객체에 저장
kakao.maps.event.addListener(clusterer, 'clusterclick', cluster => {
RealEstateDispatch({
type: 'GET_SELECTED_ESTATE',
selected: realEstate.filter(estate => {
return cluster
.getMarkers()
.map(x => x.getPosition())
.find(
qa =>
estate.lat.toFixed(12) === qa.Ma.toFixed(12) &&
estate.lng.toFixed(12) === qa.La.toFixed(12)
);
}),
});
});
// 주소 검색 콜백 함수
const placeSearch = (result, status) => {
if (status === kakao.maps.services.Status.OK) {
setSearchModal({ ...searchModal, addressResult: result });
return;
}
};
const searchTextHandler = ({ target }) => {
if (2 <= target.value.length) {
setSearchModal({ ...searchModal, isOn: true, searchText: target.value });
setTradeTypeModal(false);
setRoomTypeModal(false);
// 검색 시 주소 검색 결과 배열 state 추가
places.keywordSearch(target.value, placeSearch);
} else {
setSearchModal({
...searchModal,
isOn: false,
searchText: '',
addressResult: [],
});
}
};
// 매물 검색 fetch
const getSearchResult = () => {
// 한국어 검색어를 헤더에 넣어서 non ISO-8859-1 code point 에러 발생. / Esint가 자동으로 쉼표를 지우기 때문으로 추정
// 쿼리 스트링으로 대체
fetch(
`${BASE_URL}/estates/content/search?search=${searchModal.searchText}`,
{
method: 'GET',
}
)
.then(res => res.json())
.then(data => {
setSearchModal({ ...searchModal, searchResult: data });
});
};
// 매물 검색 업데이트 useEffect
useEffect(() => {
if (!searchModal.searchText || searchModal.searchText.length < 2) {
return;
}
getSearchResult();
}, [searchModal.searchText]);
context.js
로 인풋입력 관리react-daum-postcode api
를 모달 창으로 띄워서 주소 업데이트kakao map api
를 통해 해당 주소로 지도가 업데이트import React, { useContext } from 'react';
import styled from 'styled-components';
import Header from '../../components/header/Header';
import Footer from '../../components/footer/Footer';
import ManageNav from './ManageNav';
import ManageFormNotice from './ManageFormNotice';
import ManageFormRoomType from './ManageFormRoomType';
import ManageFormAddress from './ManageFormAddress';
import ManageFormSend from './ManageFormSend';
import { GlobalContextProvider } from './context';
import ManageFormDetail from './ManageFormDetail';
import ManageFormRoomInfo from './ManageFormRoomInfo';
import ManageFormTradeType from './ManageFormTradeType';
function ManageForm() {
return (
<>
<Header />
<Wrapper>
<TitleWrapper>
<Title>방내놓기</Title>
</TitleWrapper>
<ManageNav select="form" />
<GlobalContextProvider>
<ManageFormNotice />
<ManageFormRoomType />
<ManageFormAddress />
<ManageFormTradeType />
<ManageFormRoomInfo />
<ManageFormDetail />
<ManageFormSend />
</GlobalContextProvider>
</Wrapper>
<Footer />
</>
);
}
const Wrapper = styled.section`
margin: 0 auto;
padding: 0 1rem;
width: 1200px;
`;
const TitleWrapper = styled.header`
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 200px;
`;
const Title = styled.h1`
font-size: 2rem;
`;
export default ManageForm;
자식 컴포넌트 주소 업데이트/ ManageFormAddress.js
<SearchAddressBox>
<TextInput
placeholder="예 ) 번동 10-1, 강북구 번동"
ref={searchAddressValue}
onClick={e => (e.target.value = '')}
/>
<ButtonInput value="주소검색" onClick={handleShowModal} />
</SearchAddressBox>
<BorderBox>
<div className="addressText">
{' '}
<span>도로명 : </span>
{`${infoContext.address_main} ${
infoContext.building_name
? `(${infoContext.building_name})`
: ''
}`}
</div>
<div className="addressText">
<span>지 번 : </span>
{infoContext.jaddress}
</div>
</BorderBox>
<FlexDiv>
<FlexDiv check={check}>
<TextInput
placeholder="예 ) 101동"
check={check}
onChange={handleDongAddress}
/>
<DetailAdressBox marginRight="5px">동</DetailAdressBox>
</FlexDiv>
<FlexDiv flexWidth={check}>
<TextInput
placeholder="예 ) 101호"
onChange={handleHoAddress}
/>
<DetailAdressBox>호</DetailAdressBox>
</FlexDiv>
</FlexDiv>
ManageAddress의 자식 컴포넌트 - daum-postcode api 모달 창
function ManageFormPostCode({ handle, val }) {
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = 'auto';
};
}, []);
const infoDispatch = useContext(InfoDispatchContext);
const handleAddress = data => {
infoDispatch({ type: 'UPDATE_ADDRESS', address_main: data.address });
infoDispatch({ type: 'UPDATE_JADDRESS', jaddress: data.jibunAddress });
infoDispatch({
type: 'UPDATE_BUILDINGNAME',
building_name: data.buildingName,
});
val.value = data.address;
};
return (
<Outer onClick={handle}>
<Inner>
<DaumPostcode onComplete={handleAddress} onClose={handle} />
</Inner>
</Outer>
);
}
postcode 컴포넌트의 주소 검색 결과로 옆의 지도가 업데이트됩니다.
postcode와 형제 컴포넌트인 ManageFormMap.js
function ManageFormMap() {
const container = useRef(null); //지도를 담을 영역의 DOM 레퍼런스
const { kakao } = window;
const infoContext = useContext(InfoContext);
const infoDispatch = useContext(InfoDispatchContext);
const Address = infoContext.address_main;
let Lat = 0;
let Lng = 0;
const geocoder = new kakao.maps.services.Geocoder();
const options = {
center: new kakao.maps.LatLng(Lat, Lng),
level: 3,
draggable: false,
};
const marker = new kakao.maps.Marker({
position: new kakao.maps.LatLng(Lat, Lng),
});
useEffect(() => {
const map = new kakao.maps.Map(container.current, options);
marker.setMap(map);
geocoder.addressSearch(
infoContext.address_main,
function (results, status) {
if (status === kakao.maps.services.Status.OK) {
const result = results[0];
const coords = new kakao.maps.LatLng(result.y, result.x);
map.setCenter(coords);
marker.setPosition(coords);
infoDispatch({ type: 'UPDATE_LATITUDE', latitude: result.y });
infoDispatch({ type: 'UPDATE_LONGITUDE', longitude: result.x });
}
}
);
return () => {};
}, [Address]);
return (
<div
id="mapDiv"
ref={container}
style={{ width: '100%', height: '100%' }}
/>
);
}
ManageFormRoomInfo.js
전용 면적은 공급 면적보다 작아야합니다.
해당 층수는 건물 층수보다 낮아야 합니다.
const handleExclusive = e => {
if (supply_size < e) {
exclusiveSizeMRef.current.value = '';
alert('전용 면적은 공급 면적보다 클 수 없습니다.');
return;
}
InfoDispatch({
type: 'UPDATE_EXCLUSIVE_SIZE',
exclusive_size: e * 1,
});
...
// 매물 층수가 건물 전체 층수보다 높을 때
<Select
name="currentFloor"
required
onChange={e => {
if (
building_floor.slice(0, -1) * 1 <
e.target.value.slice(0, -1) * 1
) {
e.target.value = '';
alert('건물 층수보다 높을 수 없습니다.');
return;
}
InfoDispatch({
type: 'UPDATE_CURRENT_FLOOR',
current_floor: e.target.value,
});
}}
>
ManageForm
ManageSend.js
반드시 차 있어야 하는 인풋이 비어있다면 send 하지 않습니다.
const sendInfo = () => {
fetch('http://localhost:8000/estates', {
method: 'POST',
headers: { 'Content-type': 'application/json', token: token },
body: JSON.stringify(Info),
})
.then(res => {
if (!res.ok) {
throw new Error(res.statusText);
}
res.json();
})
.catch(err => alert(err))
.then(alert('매물이 등록되었습니다.'))
.then(navigate('/manage/list'));
};
const verify = () => {
const {
address_main,
address_ho,
category_id,
supply_size,
exclusive_size,
building_floor,
current_floor,
price_main,
price_deposit,
price_monthly,
heat_id,
available_date,
description_title,
description_detail,
trade_id,
} = Info;
if (
!address_main ||
!address_ho ||
!category_id ||
!supply_size ||
!exclusive_size ||
!building_floor ||
!current_floor ||
!(price_main || (price_deposit && price_monthly)) ||
!heat_id ||
!available_date ||
!description_title ||
!description_detail ||
!trade_id
) {
alert('모든 정보를 입력해주세요');
return;
}
sendInfo();
};
ManageFormRoomInfo.js
const PtoM = (e, M) => {
if (!e.target.value) {
M.current.value = '';
} else {
M.current.value = (e.target.value * 3.3).toFixed(2);
}
};
const MtoP = (e, P) => {
if (!e.target.value) {
P.current.value = '';
} else {
P.current.value = (e.target.value / 3.3).toFixed(2);
}
};
.
.
.
<div className="inner">
<div className="inner-inner">
<span>공급 면적</span>
<input
ref={supplySizePRef}
name="supplySizeP"
type="number"
onChange={e => {
PtoM(e, supplySizeMRef);
handleSupply(supplySizeMRef.current.value);
}}
/>
<span>평</span>
<input
ref={supplySizeMRef}
name="supplySize"
type="number"
onChange={e => {
handleSupply(e.target.value);
MtoP(e, supplySizePRef);
}}
/>
<span>㎡</span>
</div>