React Native에 React Query 적용하기
React Query란?
React Query는 서버 상태 관리 라이브러리로 아래와 같은 기능을 제공한다!
- 데이터 페칭, 캐싱, 동기화, 업데이트 관리
- 네트워크 요청 상태(로딩, 에러, 성공) 추적
- 서버 데이터 캐싱 및 리패칭 최적화
이번 프로젝트에서 회원가입 기능을 구현하면서, React Query를 도입했는데 연습용으로 사용해봤다. React Query는 서버와의 비동기 통신을 효율적으로 관리하고, 상태 관리가 복잡하지 않게 해주는 도구로 잘 알려져 있다. 기존에는 서버와의 비동기 통신을 처리하기 위해 fetch나 axios를 사용했었지만, React Query는 비동기 상태를 자동으로 관리해줘서 상태 변화에 따른 처리 로직을 더욱 깔끔하게 작성할 수 있었던 것 같다.
이제 react query로 firebase api를 연동해 작업해보자..
1. React Native에 React Query 설치
pnpm add @tanstack/react-query
설치 후 queryClient를 설정해야 한다. (queryClient.ts를 생성해 프로젝트 폴더 하위 바로 넣어줌.!)
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient()
2. QueryClientProvider 설정
React Query를 사용하기 위해 최상위 컴포넌트에 QueryClientProvider를 추가해야 한다.
난 _layout 파일에 넣어주었돠.
- QueryClientProvider는 React Query의 모든 기능을 하위 컴포넌트에서 사용할 수 있도록 한다.
- queryClient는 서버 상태를 관리한다.
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#fff',
},
headerTintColor: '#000',
headerTitleStyle: {
fontWeight: 'bold',
},
headerTitle: 'Fitty',
headerLeft: () => (
<Image
source={require('../assets/images/faceLogo_bgRemoved.png')}
style={{ width: 40, height: 40, marginLeft: 130 }}
/>
),
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</QueryClientProvider>
)
}
3. API 요청 구현
난 firebase를 사용 중이라 firebaseApi.ts 파일을 만들고 api 로직을 분리해줬다.
// 회원가입
export const signupUser = async (
userData: FinalSignupFormData
): Promise<void> => {
try {
// Firebase Authentication에 유저 등록
const userCredential = await createUserWithEmailAndPassword(
auth,
userData.email,
userData.password
)
const user = userCredential.user
// Firestore에 유저 정보 저장
await addDoc(collection(db, 'users'), {
uid: user.uid,
nickname: userData.nickname,
email: userData.email,
})
} catch (error) {
console.error('회원가입 실패:', error)
throw error
}
}
이제 signup.tsx 에서 회원가입 요청을 보내준다.
회원가입 폼을 제출할 때, 서버에 요청을 보내고 그에 대한 성공/실패 결과를 처리하는 방식으로 useMutation을 사용했다. React Query의 useMutation 훅은 주로 데이터 생성, 수정, 삭제와 같은 서버에 데이터를 전송하는 작업에 적합한 훅이다!
const mutation = useMutation<void, Error, FinalSignupFormData>({
mutationFn: signupUser,
onSuccess: () => {
Alert.alert('회원가입 성공!', '이제 Fitty를 사용할 수 있어요.')
router.push('/login')
},
onError: (error: Error) => {
console.error(error)
Alert.alert('회원가입 실패!', '다시 시도해주세요.')
},
})
const onSubmit = (data: SignupFormData) => {
const { confirmPassword, ...userData } = data
mutation.mutate(userData)
}
위와 같은 방식으로 성공과 실패 시에 각각 적절한 처리를 할 수 있돠!!!
난 zod + react-hook-form으로 회원가입 폼을 만들어 함께 통합해주면 된다!
type SignupFormData = z.infer<typeof signupSchema>
export type FinalSignupFormData = Omit<SignupFormData, 'confirmPassword'>
export default function Signup() {
const {
register,
handleSubmit,
setValue,
formState: { errors },
} = useForm<SignupFormData>({
resolver: zodResolver(signupSchema),
mode: 'all',
})
const mutation = useMutation<void, Error, FinalSignupFormData>({
mutationFn: signupUser,
onSuccess: () => {
Alert.alert('회원가입 성공!', '이제 Fitty를 사용할 수 있어요.')
router.push('/login')
},
onError: (error: Error) => {
console.error(error)
Alert.alert('회원가입 실패!', '다시 시도해주세요.')
},
})
const onSubmit = (data: SignupFormData) => {
const { confirmPassword, ...userData } = data
mutation.mutate(userData)
}
return (
<View style={commonStyles.container}>
<Link href="/">
<Image
source={require('../../assets/images/fullLogo_bgRemoved.png')}
style={styles.image}
/>
</Link>
<Text style={styles.title}>회원가입</Text>
<TextInput
placeholder="닉네임"
style={styles.input}
keyboardType="email-address"
{...register('nickname')}
onChangeText={(text) => setValue('nickname', text)}
/>
{errors.nickname && (
<Text style={styles.errorText}>{errors.nickname.message}</Text>
)}
<TextInput
placeholder="이메일"
style={styles.input}
keyboardType="email-address"
{...register('email')}
onChangeText={(text) => setValue('email', text)}
/>
{errors.email && (
<Text style={styles.errorText}>{errors.email.message}</Text>
)}
<TextInput
placeholder="비밀번호"
secureTextEntry
style={styles.input}
{...register('password')}
onChangeText={(text) => setValue('password', text)}
/>
{errors.password && (
<Text style={styles.errorText}>{errors.password.message}</Text>
)}
<TextInput
placeholder="비밀번호 확인"
secureTextEntry
style={styles.input}
{...register('confirmPassword')}
onChangeText={(text) => setValue('confirmPassword', text)}
/>
{errors.confirmPassword && (
<Text style={styles.errorText}>{errors.confirmPassword.message}</Text>
)}
<Button title="회원가입" onPress={handleSubmit(onSubmit)} />
<Text style={styles.footerText}>
이미 계정이 있으신가요?
<Text style={styles.linkText} onPress={() => router.push('/login')}>
로그인하러 가기
</Text>
</Text>
</View>
)
}
위와 마찬가지로 로그인도 구현해주면 된다.
// 로그인
export const loginUser = async (
userData: LoginFormData
): Promise<UserCredential> => {
try {
const userCredential = await signInWithEmailAndPassword(
auth,
userData.email,
userData.password
)
return userCredential
} catch (error) {
console.error('로그인 실패:', error)
throw error
}
}
const mutation = useMutation<UserCredential, Error, LoginFormData>({
mutationFn: loginUser,
onSuccess: () => {
Alert.alert('로그인 성공!', '오늘 운동을 완료하세요.')
router.push('/(tabs)')
},
onError: (error: Error) => {
console.error(error)
Alert.alert('로그인 실패!', '이메일 또는 비밀번호를 확인해주세요.')
},
})
const onSubmit = (data: LoginFormData) => {
mutation.mutate(data)
}
실행을 해보면...
실행도 잘 되고 firebase에도 회원이 잘 추가가 된다.!! 🥳🥳
'🔥React 뽀개기' 카테고리의 다른 글
Expo 앱 배포하기(웹 링크 + Expo QR 코드) (2) | 2025.02.15 |
---|---|
react native에 kakao map 연동하기(feat. 현재 위치 띄우기 + 키워드 검색) (2) | 2025.02.10 |
React Native와 Expo로 간편하게 앱 개발하기 (1) | 2025.02.04 |
페이지네이션 초간단 구현하기 (React) (2) | 2024.12.23 |
path를 따라가는 로딩화면 구현하기 (0) | 2024.08.16 |