jineecode

React navigation (3) 본문

React native

React navigation (3)

지니코딩 2022. 5. 23. 13:17

React navigation에서 모달 개발하기!

 

다음과 같은 경우에 사용합니다.

- 여러 개의 Screen에서 X를 눌렀을 때, ~중지하시겠나요?공통 알럿 모달을 띄워줄 때.

- Bottom tab을 눌렀을 때, 페이지 이동을 막고 현재는 이용할 수 없습니다 등의 알럿 모달을 띄워줄 때.

기본적인 원리는, Modal을 가진 Screen을 하나의 Screen으로 보되, 현재 페이지에서 이동을 막아주고 Screen만 띄워주는 방식입니다.

 

1. 여러 개의 Screen에서 X를 눌렀을 때, ~중지하시겠나요?공통 알럿 모달을 띄워줄 때

  • X 버튼에 onPress callback 함수 호출.
const SignUpHeaderRight = () => {
  const navigation = useNavigation();

  return {
    title: '회원가입',
    headerBackTitleVisible: false,
    headerTitleAlign: 'left',
    headerRight: () => (
      <GoToButtonWrapper activeOpacity={0.5}>
        <IconCloseImg
          onPress={(e) => {
            e.preventDefault(); // 페이지 이동을 막아줍니다.
            navigation.navigate('SignUpModal'); // SignUpModal로 navigate 됩니다.
          }}
        />
      </GoToButtonWrapper>
    ),
    presentation: 'transparentModal', // react-navigation이 제공해주는 모달 옵션입니다. 배경을 불투명하게 만들어줘요~
  };
};

 

  • 다들 공통 모달 컴포넌트는 개발하잖아요. 그렇죠? 없다면 만듭시다...
import React, {useState} from 'react';
import {CommonModal} from '~/components/atoms/Modal';
import {useDispatch} from 'react-redux';

interface IProps {
  navigation: any;
  modalTextLabel: string;
  clearDispatch?: any;
  navigationScreen?: string;
  isTwoBtn: boolean;
  isSubTitle: boolean;
  label: string;
}

const NavigationModal = ({
  navigation,
  modalTextLabel,
  clearDispatch,
  navigationScreen,
  isTwoBtn,
  isSubTitle,
  label,
}: IProps) => {
  const dispatch = useDispatch();

  const [isVisibleModal, setIsVisibleModal] = useState(true);

  return (
    <>
      {isVisibleModal && (
        <CommonModal
          isTwoBtn={isTwoBtn}
          cancelLabel="취소"
          label={label}
          handleCancel={() => navigation.goBack()}
          modalTextLabel={modalTextLabel}
          isSubTitle={isSubTitle}
          modalSubTextLabel={'작성하신 정보는 저장되지 않습니다'}
          handleConfirm={() => {
            if (!isTwoBtn) {
              navigation.goBack();
            } else {
              if (clearDispatch) {
                dispatch(clearDispatch);
              }

              if (navigationScreen === 'LogIn') {
                navigation.navigate(navigationScreen);
              } else {
                navigation.pop(2);
              }
            }
            setIsVisibleModal(false);
          }}
        />
      )}
    </>
  );
};

export default NavigationModal;
  • SignUpModal 은 겉보기엔 Modal처럼 보이지만, 엄연히 하나의 Screen입니다. 모달처럼 '눈속임'을 하는 거죠. 이렇게 만든 SignUpModal를 Screen으로 넣어줍니다.
export const AuthStack = () => {
  return (
    <Stack.Navigator>
      {Features({
        name: 'LogIn',
        screen: LogIn,
        customOptions: {headerShown: false},
      })}
      {Features({
        name: 'SignUpModal',
        screen: SignUpModal,
        customOptions: SignUpHeaderRight(),
        isDepth: true,
        isRight: true,
      })}
      {Features({
        name: 'SignUpTerms',
        screen: SignUpTerms,
        customOptions: SignUpHeaderRight(),
        isDepth: true,
        isRight: true,
      })}
    </Stack.Navigator>
  );
};
  • 처음에 만든 SignUpHeaderRight에 있는 컴포넌트에서, X 버튼을 누르면 SignUpModal Screen으로 이동(되는것처럼) 시키기 위해 Stack.NavigatorSignUpModal Screen을 넣어주는 작업이 꼭 필요합니다. 그렇지 않으면 내비게이터가 찾지 못해요.

 

2. Bottom tab을 눌렀을 때, 페이지 이동을 막고 현재는 이용할 수 없습니다 등의 알럿 모달을 띄워줄 때.

 

홈, 날씨, 설정 세 개의 Tab이 있다고 가정해봅시다.

날씨탭을 현재 이용할 수 없을 때, 이를 어떻게 막고 모달을 띄워줄 수 있을까요?

홈에서 날씨를 눌렀을 때도, 설정에서 날씨를 눌렀을때도 같은 모달을 보여주어야 합니다.

일단 탭들이 모두 모여있는 BottomTab에서 뭔가를 해주어야 할 것 같습니다. 

1번처럼 Screen을 끼워넣어보려해도, Bottom에 모달 Tab이 생겨버리게 됩니다.

 

 

다음은 서비스를 등록하기 전이면 날씨탭을 이용하지 못하도록 막았습니다.

export const BottomTab = () => {
  const {hasFarmlands} = useSelector((state) => state.users);

  return (
    <Tab.Navigator
      initialRouteName="HomeTab"
      screenOptions={{
        headerShown: false,
        tabBarLabelStyle: {
          fontSize: Theme.fontSize(12),
        },
        tabBarStyle: {
          height: 56,
          paddingTop: Theme.height(5),
          paddingBottom: Theme.height(5),
        },
        tabBarHideOnKeyboard: true,
        tabBarActiveTintColor: '#005500',
      }}>
      <Tab.Screen
        name="HomeTab"
        component={HomeTab}
        options={{
          tabBarLabel: '홈',
          tabBarIcon: ({focused}) => (focused ? <IconHomeAct /> : <IconHome />),
        }}
      />
      <Tab.Screen
        name="WeatherTab"
        component={WeatherTab}
        listeners={({navigation}) => ({
          tabPress: (e) => {
            if (!hasFarmlands) {
              e.preventDefault();
              navigation.navigate(`NoFarmModal${navigation.getState().index}`);
            }
          },
        })}
        options={{
          tabBarLabel: '날씨',
          tabBarIcon: ({focused}) =>
            focused ? <IconWeatherAct /> : <IconWeather />,
        }}
      />
      <Tab.Screen
        name="SettingTab"
        component={SettingTab}
        options={{
          tabBarLabel: '설정',
          tabBarIcon: ({focused}) =>
            focused ? <IconSettingAct /> : <IconSetting />,
        }}
      />
    </Tab.Navigator>
  );
};
  • 포인트는 Screen 컴포넌트의 listeners prop에 있습니다. 원리는 1번과 같습니다.
        listeners={({navigation}) => ({
          tabPress: (e) => {
            if (!hasFarmlands) {
              e.preventDefault();
              navigation.navigate(`NoFarmModal${navigation.getState().index}`);
            }
          },
        })}

 

  • tabPress callback이 작동하면 e.preventDefault(); 으로 페이지 이동을 막고 navigate 페이지로 넘깁니다.
  • NoFarmModal${navigation.getState().index} 는 HomeTab, SettingTab 안에 있는, Screen에 들어있습니다.
const NoFarmModal = ({navigation}) => {
  return (
    <NavigationModal
      isTwoBtn={false}
      isSubTitle={false}
      navigation={navigation}
      modalTextLabel={`날씨 정보를 확인 하시려면 서비스 등록부터 해주세요`}
      label="확인"
    />
  );
};

export const HomeTab = ({navigation}) => {

  return (
    <Stack.Navigator>
      {Features({
        name: 'Home',
        screen: Home,
        initialParams: {init: 'init'},
        customOptions: {
          title: '홈',
          headerTitleAlign: 'center',
          headerLeft: () => (
            <Touch activeOpacity={0.5}>
              <GoToButton screenName="User" />
            </Touch>
          ),
        },
      })}
      {Features({
        name: 'NoFarmModal0',
        screen: NoFarmModal,
        customOptions: {
          title: '홈',
          headerLeft: () => null,
          headerRight: () => (
            <NotificationButtonWrapper
              activeOpacity={0.5}
              onPress={() => navigation.navigate('Notification')}>
              <IconNotiCheck />
            </NotificationButtonWrapper>
          ),
          presentation: 'transparentModal',
        },
      })}
    </Stack.Navigator>
  );
};
const NoFarmModal = ({navigation}) => {
  return (
    <NavigationModal
      isTwoBtn={false}
      isSubTitle={false}
      navigation={navigation}
      modalTextLabel={`날씨 정보를 확인 하시려면 서비스 등록부터 해주세요`}
      label="확인"
    />
  );
};

export const SettingTab = ({navigation, route}: any) => {

  return (
    <Stack.Navigator>
      {InquiryTab(navigation, 'Setting')}
      {Features({
        name: 'NoFarmModal2',
        screen: NoFarmModal,
        customOptions: {
          title: '홈',
          headerLeft: () => null,
          headerRight: () => (
            <NotificationButtonWrapper
              activeOpacity={0.5}
              onPress={() => navigation.navigate('Notification')}>
              <IconNotiCheck />
            </NotificationButtonWrapper>
          ),
          presentation: 'transparentModal',
        },
      })}
    </Stack.Navigator>
  );
};

 

기본적으로 React-navigation은 'Name' 찾아 페이지 이동을 합니다. 같은 Name을 가진 NoFarmModal을 각각의 Tab Screen에 넣으면 페이지 이동이 꼬일 수 있습니다. 때문에 같은 컴포넌트가 부여되더라도 Name은 각 Navigator마다 구별되도록 써주어야 합니다. 이를 위해 뒤에 index를 넣어줍니다.

'React native' 카테고리의 다른 글

react-native android 배포  (0) 2022.05.24
react-native toast 사용하기 [react-native-toast-message]  (0) 2022.05.24
React navigation (2)  (0) 2022.05.23
React navigation (1)  (0) 2022.05.23
RN 반응형 디자인 적용법  (0) 2022.02.17
Comments