구현 예제를 보려면 "더보기" 클릭!
더보기
Modal 구현 예제 코드
import AsyncStorage from "@react-native-async-storage/async-storage";
import axios from "axios";
import React, { useEffect, useRef, useState } from "react";
import { Alert, Animated, Button, Dimensions, Modal, PanResponder, Pressable, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View } from "react-native";
import config from "../../../project.config"
export default function PurchaseRecipe({navigation}:any) {
// 로그인 세션 정보
const [loginData, setLoginData] = useState(Object);
// 보유코인 및 레시피 가격 세팅
const [userCoin, setUserCoin] = useState(0);
const [recipePrice, setRecipePrice] = useState(3000);
useEffect(() => {
const getLoginData = async () => {
let data = await AsyncStorage.getItem("loginData");
try {
if (data !== null) {
let loginSessionData = JSON.parse(data);
setLoginData(loginSessionData);
setUserCoin(loginData.memberCoin);
}
} catch (err) {
console.log(err);
}
}
getLoginData();
}, [userCoin]);
// 레시피 구매 버튼 tap할 때 구매 요청을 보낼 함수
const requestPurchaseRecipe = async () => {
await axios.post(config.address + "coin/useCoin", null, {params: {
memberId: loginData.memberId,
docsSeq: 1,
coinCount: recipePrice
}})
.then((res) => {
console.log(res.data);
Alert.alert("구매 성공", "구매가 완료되었습니다.", [
{text: "확인", onPress: () => navigation.navigate("Home")}
]);
})
.catch((err) => console.log(err));
}
// 모달의 띄워짐을 조정할 변수
const [isModalVisible, setIsModalVisible] = useState(false);
// 모달이 띄워질 때 설정될 속성들
const screenHeight = Dimensions.get('screen').height;
const panY = useRef(new Animated.Value(screenHeight)).current;
const translateY = panY.interpolate({
inputRange: [-1, 0, 1],
outputRange: [-1, 0, 1]
});
const resetBottomSheet = Animated.timing(panY, {
toValue: 0,
duration: 300,
useNativeDriver: true
});
const closeBottomSheet = Animated.timing(panY, {
toValue: screenHeight,
duration: 300,
useNativeDriver: true
});
const panResponder = useRef(PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: (event, gestureState) => panY.setValue(gestureState.dy),
onPanResponderRelease: (event, gestureState) => {
if (gestureState.dy > 0 && gestureState.vy > 1.5) {
closeModal();
} else {
resetBottomSheet.start();
}
}
})).current;
const closeModal = () => {
closeBottomSheet.start(() => setIsModalVisible(false));
}
useEffect(() => {
if (isModalVisible) {
resetBottomSheet.start();
}
}, [isModalVisible]);
return (
<View style={styles.flexible}>
{/* 모달을 띄우게 할 버튼 */}
<View style={styles.alignContentsCenter}>
<Button
title="버튼을 탭하여 레시피 구매"
onPress={() => setIsModalVisible(!isModalVisible)}
/>
</View>
{/* 모달 */}
<Modal
visible={isModalVisible}
animationType={"slide"}
transparent={true}
statusBarTranslucent={true}
>
<Pressable
style={styles.modalOverlay}
onPress={() => setIsModalVisible(!isModalVisible)}
>
<TouchableWithoutFeedback>
<Animated.View
style={{...styles.bottomSheetContainer,
transform: [{ translateY: translateY }]}}
{...panResponder.panHandlers}
>
{/* 모달에 들어갈 내용을 아래에 작성 */}
<View>
<Text style={modalInnerStyle.recipeTitle}>레시피 제목</Text>
<Text style={[modalInnerStyle.coin, {color: '#00f'}]}>보유코인 {userCoin}원</Text>
<Text style={[modalInnerStyle.coin, { color: '#f00'}]}>구매가격 {recipePrice}원</Text>
<TouchableOpacity
style={modalInnerStyle.modalBtn}
onPress={() => {
Alert.alert("구매 의사 재확인", "보유하고 있는 코인으로 레시피를 구매하시겠습니까? \n 코인으로 구매한 상품은 환불이 어렵습니다.", [{text: "취소"}, {text: "확인", onPress: () => requestPurchaseRecipe()}])
}}
>
<Text style={modalInnerStyle.btnText}>버튼을 탭하여 구매하기</Text>
</TouchableOpacity>
<Text style={modalInnerStyle.warningText}>레시피는 결제완료시 환불이 어렵습니다.</Text>
</View>
</Animated.View>
</TouchableWithoutFeedback>
</Pressable>
</Modal>
</View>
)
}
const styles = StyleSheet.create({
flexible: {
flex: 1
}, // flex 속성 지정
alignContentsCenter: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}, // 가로세로 중앙정렬
modalOverlay: {
flex: 1,
justifyContent: 'flex-end',
backgroundColor: 'rgba(0, 0, 0, 0.4)'
}, // 모달이 띄워졌을 때 화면을 어둡게 하기 위한 오버레이
bottomSheetContainer: {
height: 300,
backgroundColor: '#fff',
borderTopLeftRadius: 7,
borderTopRightRadius: 7,
padding: 20
}, // 모달 스타일
})
const modalInnerStyle = StyleSheet.create({
recipeTitle: {
fontSize: 22,
fontWeight: '700'
},
coin: {
fontSize: 17,
fontWeight: '700',
paddingTop: 10,
textAlign: 'right'
},
modalBtn: {
padding: 10,
backgroundColor: '#4852c7',
borderRadius: 7,
marginTop: 30,
marginBottom: 30
}, // 모달 내 결제버튼
btnText: {
padding: 6,
fontSize: 17,
fontWeight: '700',
color: '#fff',
textAlign: 'center'
},
warningText: {
color: '#f00',
textAlign: 'center',
fontSize: 16,
}
})
앱 내에서 결제를 하거나 부가적인 정보를 띄워줄 때 Alert
도 좋지만 화면 아래에서 스르륵하고 올라오는 모달을 구현해야 할 때가 있다.
리액트 네이티브에서는 Modal
이라는 컴포넌트를 모듈로 지원하고 있으며 각 속성을 부여하여 사용할 수 있다.
React Native ver. 0.68 기준
예시
import React, { useState } from "react";
import { Alert, Modal, Pressable, Text, Pressable, View } from "react-native";
export default function App() {
const [isModalVisible, setIsModalVisible] = useState(false);
return (
<Modal
animationType={"slide"}
transparent={true}
visible={isModalVisible}
onRequestClose={() => {
isModalVisible(!isModalVisible)
console.log("modal appearance")
}}
>
<Text>Modal is appearance!</Text>
<Pressable
onPress={() => {
Alert.alert("Modal", "모달이 나타남...")
}}
>
<Text>View Alert!</Text>
</Pressable>
</Modal>
}
사용된 속성 4가지
구분 | 의미하는 것 | 값 |
---|---|---|
animationType |
모달이 애니메이션 되는(나타나고 사라지는) 방식 | slide , fade , none(Default) |
transparent |
모달이 화면 전체를 채워 등장하는지 여부 | boolean |
visible |
모달 표시 여부 | boolean |
onRequestClose |
뒤로가기 버튼(Android) 또는 메뉴버튼(Apple TV)을 선택할 때 실행할 함수 | function |
'Study > React' 카테고리의 다른 글
[React.js] NavBar 예제 (0) | 2022.04.28 |
---|---|
[React.js] 둥근 체크박스 만들기 (0) | 2022.04.26 |
[React Native] useEffect의 dependency array 사용 시 주의점 (0) | 2022.04.02 |
[React Native] 다른 페이지로 값 전달하기 (0) | 2022.03.23 |
[React Native] axios를 사용한 비동기 통신과 장면 전환 (1) | 2022.03.11 |