카테고리 없음
SelectView.tsx(출발/도착지 입력창)
김딸기*
2024. 12. 16. 02:58
이 코드는 React Native를 사용하여 위치 기반 검색 기능을 구현한 앱의 일부분입니다. 주요 기능은 사용자의 현재 위치를 얻고, 이를 기반으로 검색어와 일치하는 가맹점을 필터링하여 리스트로 보여주는 것입니다. 이제 코드를 여러 부분으로 나누어 기능을 설명하겠습니다.
1. 상태 관리 (State Management)
const SelectView = () => {
const [searchQuery, setSearchQuery] = useState('');
const [currentPosition, setCurrentPosition] = useState<{ latitude: number; longitude: number } | null>(null);
const [loading, setLoading] = useState(false);
const [filteredBranches, setFilteredBranches] = useState<Branch[]>([]);
- searchQuery: 사용자가 검색창에 입력한 검색어를 저장.
- currentPosition: 사용자의 현재 위치를 저장 (위도, 경도).
- loading: API 호출 중 로딩 상태를 관리.
- filteredBranches: 필터링된 지점들을 저장.
2. 위치 정보 가져오기 (Getting User Location)
// 사용자의 위치를 가져오는 함수
const getUserLocation = () => {
setLoading(true);
Geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
setCurrentPosition({ latitude, longitude });
setLoading(false);
},
(error) => {
console.error(error);
Alert.alert('위치 정보를 가져올 수 없습니다.', '위치 권한을 허용해주세요.');
setCurrentPosition({ latitude: 37.5665, longitude: 126.9780 }); // 임시 값 (서울 좌표)
setLoading(false);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
};
- getUserLocation: 사용자의 위치 정보를 가져오는 함수입니다.
- 위치 정보가 성공적으로 얻어지면 currentPosition에 저장하고, 로딩 상태를 종료합니다.
- 위치 정보를 가져올 수 없는 경우, 오류 메시지를 출력하고 기본값(서울)을 설정합니다.
3. 위치 권한 요청 (Request Location Permission)
// 컴포넌트가 처음 마운트될 때 사용자의 위치를 가져옴
useEffect(() => {
const requestLocationPermission = async () => {
const permission = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
if (permission === RESULTS.GRANTED) {
getUserLocation(); // 권한이 허용되면 위치 가져오기
} else {
Alert.alert('위치 권한이 필요합니다', '위치 권한을 허용해주세요.');
}
};
requestLocationPermission();
}, []);
- requestLocationPermission: 위치 권한을 요청하는 함수입니다.
- 권한이 허용되면 getUserLocation을 호출하여 사용자의 위치를 가져옵니다.
- 권한이 거부되면 사용자에게 알림을 띄웁니다.
4. API 호출 (Fetching Data from API)
// 검색어와 현재 위치를 사용해 API 호출
const fetchBranches = async () => {
if (!currentPosition) {
Alert.alert('위치 정보가 없습니다', '위치를 가져오는 중입니다.');
return;
}
setLoading(true);
try {
const { latitude, longitude } = currentPosition;
const url = `http://54.92.160.65/api/search/${longitude}/${latitude}/${searchQuery}`;
console.log('API URL:', url); // URL 확인
// 실제 API 호출
const response = await axios.get<{ items: Branch[] }>(url);
// 응답 구조 확인
console.log('API Response:', response); // 전체 응답 객체를 확인
console.log('API Data:', response.data); // 응답 데이터가 정확히 있는지 확인
// 응답에서 items 필드가 없으면 빈 배열로 처리
const items = response.data?.items || [];
// 필터된 데이터를 설정
const filteredData = items.filter((branch) =>
branch.title.toLowerCase().includes(searchQuery.toLowerCase())
);
setFilteredBranches(filteredData);
console.log('Filtered branches:', filteredData); // 데이터가 제대로 들어가는지 확인
} catch (error) {
if (axios.isAxiosError(error)) {
// 응답에서 status 코드에 따른 오류 처리
if (error.response) {
console.error(`Axios error (status ${error.response.status}):`, error.response.data);
} else {
console.error('Axios error (no response):', error.message);
}
} else {
console.error('Unexpected error:', error); // 일반적인 오류를 처리
}
} finally {
setLoading(false);
}
};
- fetchBranches: 사용자의 현재 위치와 검색어를 기반으로 API를 호출하여 가맹점 정보를 받아오는 함수입니다.
- 위치 정보가 없으면 오류 메시지를 띄우고 종료합니다.
- axios를 사용하여 API를 호출하고, 검색어와 일치하는 지점들을 필터링하여 filteredBranches에 저장합니다.
5. 검색어 변경 시 데이터 필터링 (Handling Search Query Change)
// searchQuery가 변할 때마다 fetchBranches 호출
useEffect(() => {
setFilteredBranches([]); // 검색어가 바뀌면 이전 검색 결과를 초기화
if (searchQuery) {
fetchBranches(); // searchQuery가 변경되면 검색 시작
}
}, [searchQuery]);
- useEffect: searchQuery가 변경될 때마다 fetchBranches를 호출하여 새로운 검색 결과를 가져옵니다.
- 검색어가 없으면 filteredBranches를 초기화하고, 검색어가 있으면 API를 호출합니다.
6. 검색어 강조 (Highlighting Search Term)
// 검색어와 일치하는 부분을 강조하는 함수
const highlightText = (text: string | undefined, query: string) => {
if (!text || !query) return <Text>{text}</Text>; // text나 query가 없으면 그냥 반환
const index = text.toLowerCase().indexOf(query.toLowerCase()); // 검색어가 포함된 위치 찾기
if (index === -1) return <Text>{text}</Text>; // 검색어가 없으면 하이라이트 없이 그냥 반환
const beforeMatch = text.slice(0, index);
const match = text.slice(index, index + query.length);
const afterMatch = text.slice(index + query.length);
return (
<Text>
{beforeMatch}
<Text style={{ color: 'skyblue' }}>{match}</Text>
{afterMatch}
</Text>
);
};
- highlightText: 검색어와 일치하는 부분을 하이라이트하는 함수입니다.
- 검색어가 포함된 부분을 찾아 skyblue 색상으로 강조 표시합니다.
7. UI 렌더링 (Rendering the UI)
return (
<View style={styles.container}>
{/* 검색 창과 아이콘을 감싼 View */}
<View style={styles.searchContainer}>
<TouchableOpacity onPress={() => console.log('Back button pressed')}>
<BackIcon />
</TouchableOpacity>
<TextInput
style={styles.searchInput}
placeholder="입력해주세요"
value={searchQuery}
onChangeText={setSearchQuery} // 사용자가 입력할 때마다 searchQuery 갱신
/>
</View>
{/* 로딩 표시 */}
{loading ? (
<ActivityIndicator size="large" color="#0000ff" />
) : filteredBranches.length === 0 ? (
<Text style={styles.noResultsText}>검색 결과가 없습니다.</Text>
) : (
<FlatList
data={filteredBranches}
keyExtractor={(item: Branch) => item.title || 'no-title'} // title이 없을 경우 'no-title'을 key로 사용
renderItem={({ item }: { item: Branch }) => (
<View style={styles.itemContainer}>
<Text style={styles.name}>{highlightText(item.title || '???', searchQuery)}</Text>
<Text style={styles.address}>{item.address || 'No address available'}</Text>
<View style={styles.bottomRow}>
<Text style={styles.distance}>{item.distance ? `${item.distance} km` : 'Unknown distance'}</Text>
</View>
</View>
)}
/>
)}
</View>
);
};
- 검색창과 결과 목록을 렌더링합니다.
- 검색어 입력창과 BackIcon을 포함한 TouchableOpacity를 보여줍니다.
- loading 상태에 따라 로딩 표시(ActivityIndicator) 또는 검색 결과를 보여줍니다.
- FlatList를 사용하여 필터링된 지점(filteredBranches) 목록을 표시합니다.
- highlightText를 사용하여 검색어가 포함된 부분을 강조합니다.
8. 스타일 (Styling)
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
backgroundColor: '#fff',
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
},
searchInput: {
flex: 0.95, // 검색창이 가능한 넓은 공간을 차지하도록 설정
height: 40,
borderColor: '#ccc',
borderWidth: 1,
borderRadius: 5,
marginBottom: 10,
marginTop: 10,
paddingLeft: 10,
},
itemContainer: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
name: {
fontSize: 16,
fontWeight: 'bold',
},
address: {
color: '#666',
marginTop: 5,
},
bottomRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 5,
},
distance: {
color: '#333',
},
noResultsText: {
textAlign: 'center',
fontSize: 18,
color: '#888',
marginTop: 20,
},
});
- 앱 UI의 스타일을 정의하는 부분입니다. 각 요소에 대한 크기, 배치, 색상 등을 설정합니다.
결론
이 코드는 사용자의 위치 정보를 기반으로 검색어에 맞는 지점들을 필터링하여 화면에 출력하는 기능을 제공합니다. 사용자의 위치를 가져오기 위한 권한 요청, API 호출을 통한 지점 정보 검색, 검색어 강조 및 로딩 상태 처리가 주요 기능입니다.