jineecode
React navigation (3) 본문
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.Navigator에 SignUpModal 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 |