[React] 뒤로가기 시 상태 유지하기 ( useNavigationType, popstate )

2024. 11. 15. 18:15·React

TLDR

이번에는 Tab Navigation을 서비스를 개발하며 개선한 부분에 대해서 얘기해보려고 합니다. 그저 "탭의 상태" 에 따른 표시되는 UI가 변경되는 정도의 컴포넌트로 인식하고 있었지만, 사용자측면을 고려하고 베타테스트를 진행하면서 저 "상태"를 자연스럽게 관리하는 것에 대해 고민하고 개선한 내용과 그 로직에 대해 공유하려고 합니다.

 

초기 로직 : UI와 그 기능에 집중

처음 개발한 Tab Navigation은 UI와 Highligt Animation 등에 더 신경을 많이 썼습니다. 다른 페이지에서도 활용 될 수 있게끔, 탭의 갯수와 상관없이 UI를 적용할 수 있게끔 고민했고, 탭의 상태는 크게 신경쓰지 않았습니다.

컴포넌트의 Props를 통해 tab의 정보를 배열로 받았으며, tab navigation에서 선택된 탭의 상태를 useState를 통해 관리 했으며, Props를 통해 공유했습니다.

 

문제 1 : 활성화 된 탭의 상태가 계속해서 초기화 된다

useState로 활성화 된 탭을 관리하니 당연한 일이지만, 다른 페이지로 이동하거나 해당페이지 내 상세페이지로 들어갔다가 나올 때면 해당 탭이 초기화 되어있어서 사용자 불편함을 유발했습니다. 웹에서도 불편하겠지만, 웹뷰를 통해 앱으로 서비스를 지원하는 경우 그 불편함과 어색함이 더 부각되어 빠르게 개선이 필요했습니다.

그래서 URL params에 활성화된 tab을 기록하여 뒤로가기 stack에 남게끔 수정했습니다. URL params에 남아있다면 다른 어딘가로 이동했다가 와도 그 기록을 통해 기존에 활성화 되어 있던 탭으로 돌아 갈 수 있게끔 하여, 사용자의 불편함을 해결하였습니다.

 

import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';

function TabNavigation() {
	const [ query, setQuery ] = useSearchParams();
    
    useEffect(() => {
    	query.set('tab', tab상태);
        setQuery(query);
    }, [tab 상태])
    
    ...
}

 

문제 2 : 뒤로가기 stack이 너무 많이 쌓임

위 방법을 통해 해결한건 사용자 입장에서 다른 불편함을 유발했습니다. 탭이 변경될 때 마다 뒤로가기 stack이 쌓이게 된 것 입니다. 뒤로가기를 눌러도 이전 탭, 화면으로 돌아가는게 아닌 tab navigation 화면그대로 URL 만 되돌아 가는 현상이 발생했고 URL이 보이지 않는 앱 입장에서는 더욱 치명적으로 다가왔습니다.

위 사진은 쌓여있는 뒤로가기 stack의 예시입니다. 사용자 입장에서 tab Navigation을 다룬후 pageC로 이동했다가 다시 뒤로가기를 통해 pageA로 가려면? pageB?tab=3 -> tab=2 -> tab=1 -> pageB 라는 과정을 통해 pageB에 뒤로가기 4번 시도하는 동안 갖혀있게 됩니다.

 

문제 재 정의

URL params를 활용하는 방식의 한계를 알았으니 다른 방식으로의 수정이 필요했습니다. 기존 useState방식은 상태가 유지되지 못하니, 전역상태와 같은 방법을 고민하기 이전에 해결해야하는 문제에 대해 구체적으로 정의가 필요했습니다.

 

Tab Navigation 의 상태저장 예시

  1. tab navigation page로 정상적으로 이동했다면? "탭의 상태가 초기화" 되야 한다.
  2. 다른 페이지 / 심지어 외부페이지 이동했다가 "뒤로가기"를 해서 페이지로 돌아온다면 "탭의 상태 유지"가 되야한다
  3. 새로고침 시, 탭 유지

 

LocalStorge

서비스 내부에는 아예 외부 다른 페이지로 이동하는 링크가 있습니다. 이런 부분까지 고려한다면, 상태 관리는 localStorage에서 하는 게 맞다고 판단했습니다.

 

뒤로가기 상태 구분하기

tab navigation이 있는 페이지에서 위 문제를 해결하기 위해서는? 페이지를 접근할때 뒤로가기로 접근했는지, 아니면 페이지로 새롭게 접근했는지를 구분할 수 있어야 합니다.

  • window.addEventListener('popstate') : 뒤로가기 할 때 실행
  • useNavigationType(): react-router-dom 내 hooks로 어떤 동작을 통해 페이지로 접근했는지 확인

해당 페이지에 어떤 동작으로 접근했는지 확인하기 위해 window.history.state를 사용하려고 한적이 있으나, React SPA 때문인지 예상대로 동작하지 않았습니다. performance.navigation.type 도 마찬가지로 원하는데로 분류할 수 없어서 위 두가지 방법을 엮어서 뒤로가기 상태를 구분했습니다.

 

코드

import React, { useState, useEffect } from 'react';
import { useLocation, useNavigationType } from 'react-router-dom';

const TabContent = ({ tab }) => (
  <div>
    <h2>{tab} 페이지 내용</h2>
  </div>
);

const TabNavigation = () => {
  const isValidTab = (tab) => ['tab1', 'tab2', 'tab3'].includes(tab);

  // 현재 네비게이션 타입과 위치를 가져옵니다.
  const navigationType = useNavigationType();
  const location = useLocation();

  const initialTab =
    navigationType === 'POP'
      ? localStorage.getItem('activeTab') || 'tab1' // 뒤로가기인 경우 저장된 탭 유지
      : 'tab1'; // 새로고침 또는 직접 접근 시 기본값 설정

  const [activeTab, setActiveTab] = useState(
    isValidTab(initialTab) ? initialTab : 'tab1'
  );
  
   useEffect(() => {
    if (navigationType !== 'POP') {
      setActiveTab('tab1');
      localStorage.removeItem('activeTab');
    }
  }, []);

  useEffect(() => {
    const restoredTab = localStorage.getItem('activeTab');
    if (navigationType === 'POP' && isValidTab(restoredTab)) {
      setActiveTab(restoredTab);
    }

    const handleTabChange = (tab) => {
      setActiveTab(tab);
      localStorage.setItem('activeTab', tab);
    };

    return () => {
      handleTabChange(activeTab); // 컴포넌트 언마운트 시 현재 탭 저장
    };
  }, [location, navigationType]);

  const handleTabClick = (tab) => {
    setActiveTab(tab);
    localStorage.setItem('activeTab', tab); // 탭 상태를 localStorage에 저장
  };

  return (
    <div>
      <div>
        <button
          onClick={() => handleTabClick('tab1')}
          className={activeTab === 'tab1' ? 'active' : ''}
        >
          탭 1
        </button>
        <button
          onClick={() => handleTabClick('tab2')}
          className={activeTab === 'tab2' ? 'active' : ''}
        >
          탭 2
        </button>
        <button
          onClick={() => handleTabClick('tab3')}
          className={activeTab === 'tab3' ? 'active' : ''}
        >
          탭 3
        </button>
      </div>
      <div>
        <TabContent tab={activeTab} />
      </div>
    </div>
  );
};

const App = () => (
  <div>
    <h1>탭 네비게이션 예제</h1>
    <TabNavigation />
  </div>
);

export default App;

 

마무리

지금까지 TabNavigation 의 상태유지를 위해 개선한 내용에 대해 소개해 드렸습니다. 이번 문제를 해결하기 위해 React에서 페이지 상태를 분류할 수 있는 다양한 이벤트타입과 hooks, 로직 등에 대해서 새롭게 알게된 부분이 많았던 것 같습니다.

저작자표시 변경금지 (새창열림)

'React' 카테고리의 다른 글

React에서 안전하게 HTML 렌더링하기: dangerouslySetInnerHTML의 대안  (0) 2025.02.26
[React] 비즈니스 / 도메인 로직을 통해 코드 응집도 높이기  (3) 2024.12.03
[React] 웹뷰로 연결된 Android Stuiod / IOS 에 데이터 주고받기 ( 브릿지 통신 )  (0) 2024.11.11
[AWS] Sub Domain 연결하기  (5) 2024.10.31
[React + Vite + Nginx + Docker + AWS] 를 활용하여 배포 해봅시다!  (0) 2024.06.05
'React' 카테고리의 다른 글
  • React에서 안전하게 HTML 렌더링하기: dangerouslySetInnerHTML의 대안
  • [React] 비즈니스 / 도메인 로직을 통해 코드 응집도 높이기
  • [React] 웹뷰로 연결된 Android Stuiod / IOS 에 데이터 주고받기 ( 브릿지 통신 )
  • [AWS] Sub Domain 연결하기
설탕시럽
설탕시럽
설탕시럽의 Web Tech Blog 입니다.
  • 설탕시럽
    SugarSyrup Tech Blog
    설탕시럽
  • 전체
    오늘
    어제
    • 분류 전체보기 (47)
      • HTML & CSS (2)
      • JavaScript (6)
      • React (15)
      • BackEnd (1)
      • 회고 (5)
        • 엄브렐라 개발일지 (1)
        • 인턴 회고!!! (2)
      • 컴퓨터 기초 (4)
        • 운영체제 (2)
        • SICP (1)
      • 코딩테스트 (10)
      • 짧은 글 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    JS
    운영체제
    오블완
    티스토리챌린지
    matchmedia
    docker
    nginx
    error
    php
    level1
    인턴
    타입스크립트
    TypeScript
    코딩테스트
    에러
    css
    EC2
    프론트엔드
    리액트
    AWS
    CodingTest
    React
    회고
    SQL
    JavaScript
    자바스크립트
    document
    프로그래머스
    Element
    배포
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
설탕시럽
[React] 뒤로가기 시 상태 유지하기 ( useNavigationType, popstate )
상단으로

티스토리툴바