[Next.js14 프로젝트] Geolocation API 활용한 useGeoLocation 훅 만들기

D uuu·2024년 6월 2일
0

Next.js14 프로젝트

목록 보기
6/11
post-custom-banner

Geolocation API

어떤 서비스를 이용할때 내 위치 정보에 접근해도 되는지 묻는 창이 나올때가 있다.
동의 버튼을 누르면 내 위치 기반 데이터가 화면에 나타나는데, 이를 지원해주는 API 가 바로 Geolocation API 이다.

나는 사이트에 접속하면 내 위치 기반 주변 음식점을 불러오도록 구현하고 싶었기에 Geolocation API 를 사용하기로 했다.

기본 예제

Geolocation API 는 navigator.geolocation 을 통해 접근할 수 있다.
이때 브라우저는 사용자에게 위치 정보 접근 권한을 요청하게 되고, 사용자가 동의하는 경우 위치 정보를 가져온다.

이때 정보를 가져오는 방법으로는 두가지가 있다.

  1. geolocation.getCurrentPosition() : 장치의 현재 위치를 가져온다.
  2. geolocation.watchPosition() : 장치의 위치가 바뀔때마다 자동으로 새로운 위치를 사용해 호출할 처리기 함수를 등록한다.

두 메서드는 모두 세개의 매개변수를 받는다.

  • 위치 정보를 가져오는데 성공했을때의 실행 될 콜백함수
  • 위치 정보를 가져오는데 실패했을때의 실패 원인을 담은 콜백함수
  • 선택적으로 사용할 옵션값
  navigator.geolocation.getCurrentPosition((position) => {
    // 성공했을 때 실행될 콜백함수
    const { latitude, longitude } = position.coords;
   },
   // 실패했을 때 실행될 콜백함수
    (err) => console.error(err),
    // 옵션값
   { enableHighAccuracy: false, timeout: 5000, maximumAge: 0 }
  )
    

옵션 값으로는 아래와 같다.

{
  enableHighAccuracy: boolean  // 높은 정확도의 위치 정보를 구할지 여부
  timeout: number //  위치 정보 읽기 타임아웃(밀리초)
  maximumAge: number // 위치 정보의 캐시 기한 (0이면 캐시안함)
}

활용하기

나는 위치를 불러오는 로직을 useGeoLocation 훅으로 작성했다.
위치를 불러오는데 성공할 경우에는 위치에서 경도와 위도를 불러와 setLocation 값에 담아준다.

근데 만약에 사용자가 위치 정보를 제공하는 것을 거부한다면 어떻게 처리해야 할까?

'use client';

import { useCallback, useEffect, useState } from 'react';

type LocationType = {
    latitude: number;
    longitude: number;
};

export const useGeoLocation = () => {
    const [location, setLocation] = useState<LocationType>();

    useEffect(() => {
        const { geolocation } = navigator;

        if (!geolocation) return;

        geolocation.getCurrentPosition(
            (position) => {
                const { latitude, longitude } = position.coords;
                setLocation({
                    latitude,
                    longitude,
                });
            },
            (err) => console.log(err)
            { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }
        );
    }, []);

    return {
        curLocation: location,
    };
};

만약 사용자가 위치 정보를 동의하는 것을 거부하거나 혹은 현재 위치가 조회가 안되는 곳이라면 두번째 매개변수인 에러 콜백을 통해 처리하면 되는데, 이 부분을 에러처리 로직으로 수정해보자.

두번째 콜백 인자로는 PositionError 객체를 받는다.
PositionError 객체의 주요 속성으로는 code 가 있는데, 이를 활용해 swtich 문으로 작성해줬다.

근데 또 다른 고민이 생겼다.
만약 사용자가 위치 정보를 제공하는 것을 거부할 경우, 화면에는 어떠한 데이터도 보여지지 않게 된다. 그럴 경우엔 기본 위치를 제공하여 빈 화면이 보여지는 것을 막을 수 있다.

 const showError = useCallback((error: GeolocationPositionError) => {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                console.log('사용자가 위치 정보를 제공하는 것을 거부했습니다.');
                break;
            case error.POSITION_UNAVAILABLE:
                console.log('위치 정보를 사용할 수 없습니다.');
                break;
            case error.TIMEOUT:
                console.log('위치 정보를 가져오는 요청이 시간 초과되었습니다.');
                break;
            default:
                console.log('알 수 없는 오류가 발생했습니다.');
                break;
        }
    }, []);
    
 useEffect(() => {
        const { geolocation } = navigator;

        if (!geolocation) return;

        geolocation.getCurrentPosition(
            (position) => {
                const { latitude, longitude } = position.coords;
                setLocation({
                    latitude,
                    longitude,
                });
            },
            (err) => showError(err)
            { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }
        );
    }, []);

만약 사용자가 위치 동의를 거부할 경우, 나는 대신 화면에 띄어질 기본 위치를 미리 설정해줬다.
그리고 이 함수를 showError 함수에서 사용자가 위치 정보 제공을 거부할경우, 호출 되도록 swtich 문 안에 작성해줬다.


    const setDefaultLocation = () => {
        const defaultLatitude = 37.579293849225756;
        const defaultLongitude = 126.97798076343491;
        setLocation({
            latitude: defaultLatitude,
            longitude: defaultLongitude,
        });
    };
    
     const showError = useCallback((error: GeolocationPositionError) => {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                console.log('사용자가 위치 정보를 제공하는 것을 거부했습니다.');
                setDefaultLocation();  // 여기에서 호출!
                break;
            case error.POSITION_UNAVAILABLE:
                console.log('위치 정보를 사용할 수 없습니다.');
                break;
            case error.TIMEOUT:
                console.log('위치 정보를 가져오는 요청이 시간 초과되었습니다.');
                break;
            default:
                console.log('알 수 없는 오류가 발생했습니다.');
                break;
        }
    }, []);

최종 코드

'use client';

import { useCallback, useEffect, useState } from 'react';

type LocationType = {
    latitude: number;
    longitude: number;
};

export const useGeoLocation = () => {
    const [location, setLocation] = useState<LocationType>();
    const [isLoading, setIsLoading] = useState<boolean>(false);
    const [errorMsg, setErrorMsg] = useState<string>('');

    const setDefaultLocation = () => {
        const defaultLatitude = 37.579293849225756;
        const defaultLongitude = 126.97798076343491;
        setLocation({
            latitude: defaultLatitude,
            longitude: defaultLongitude,
        });
    };

    const showError = useCallback((error: GeolocationPositionError) => {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                setErrorMsg('사용자가 위치 정보를 제공허는 것을 거부했습니다. ');
                setDefaultLocation();
                break;
            case error.POSITION_UNAVAILABLE:
                setErrorMsg('위치 정보를 사용할 수 없습니다.');
                break;
            case error.TIMEOUT:
                setErrorMsg('위치 정보를 가져오는 요청이 시간 초과되었습니다.');
                break;
            default:
                setErrorMsg('알 수 없는 오류가 발생했습니다.');
                break;
        }
        setIsLoading(false);
    }, []);

    useEffect(() => {
        const { geolocation } = navigator;

        if (!geolocation) return;

        setIsLoading(true);

        geolocation.getCurrentPosition(
            (position) => {
                const { latitude, longitude } = position.coords;
                setLocation({
                    latitude,
                    longitude,
                });
                setIsLoading(false);
            },
            (err) => showError(err),
            { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }
        );
    }, [showError]);

    return {
        curLocation: location,
        isLoading,
        errorMsg,
    };
};
profile
배우고 느낀 걸 기록하는 공간
post-custom-banner

0개의 댓글