我正在使用 TypeScript 开发 React Native 应用程序并使用 Firebase 进行数据存储。在我的应用程序中,我有一个上下文提供程序 (NatureRecordProvider),它应该包装两个主屏幕:NatureRecordForm 和 RecordStorage。但是,当我导航到这些屏幕时,我的网络服务器中遇到以下错误:
错误信息:
完整的错误跟踪表明 useNatureRecord 正在 NatureRecordProvider 之外使用,尽管我相信我已经使用提供程序包装了必要的组件。
这是我的设置摘要:
代码示例:
const RecordNavigation = () => {
return (
<NatureRecordProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName="NatureRecordForm">
<Stack.Screen name="NatureRecordForm" component={NatureRecordForm} />
<Stack.Screen name="RecordStorage" component={RecordStorage} />
</Stack.Navigator>
</NavigationContainer>
</NatureRecordProvider>
);
};
import React, { useState, useEffect } from 'react';
import { ScrollView, Alert, View, Text, Image, StyleSheet } from 'react-native';
import { useNatureRecord } from '../../RecordLogic/RecordContext'; // Adjust the path as needed
import TopBanner from '../../RecordComponents/TopBanner';
import TabNavigation from '../../RecordComponents/TabNavigation';
import { NatureRecord as ImportedNatureRecord, NatureRecord } from '../../RecordLogic/NatureRecordInput';
interface LocalNatureRecord extends ImportedNatureRecord {
sliders: any;
}
const RecordStorage: React.FC = () => {
console.log("NatureRecordStorage component rendered");
const { environmentalRecords: rawEnvironmentalRecords, biologicalRecords: rawBiologicalRecords, fetchRecords } = useNatureRecord();
const [selectedTab, setSelectedTab] = useState<string>('environmental');
useEffect(() => {
fetchRecords();
}, []);
const environmentalRecords: LocalNatureRecord[] = rawEnvironmentalRecords.map(record => ({
...record,
id: record.id || 0, // Ensure id is present
}));
const biologicalRecords: LocalNatureRecord[] = rawBiologicalRecords.map(record => ({
...record,
id: record.id || 0, // Ensure id is present
}));
const handleClosePress = () => {
Alert.alert(
"닫기",
"정말 닫으시겠습니까?",
[
{ text: "취소", onPress: () => console.log("Close canceled"), style: "cancel" },
{ text: "확인", onPress: () => console.log("Close confirmed") }
]
);
};
const handleTabPress = (tab: string) => {
setSelectedTab(tab);
};
const { EnvironmentalRecordsList, BiologicalRecordsList }: { EnvironmentalRecordsList: React.FC<{ records: LocalNatureRecord[]; }>; BiologicalRecordsList: React.FC<{ records: LocalNatureRecord[]; }>; } = createRecordLists();
return (
<ScrollView style={{ flex: 1, padding: 0, margin: 0 }}>
<TopBanner
source={require('C:\\app\\my-app\\NatureImg\\close.svg')}
text="도감"
onClosePress={handleClosePress}
/>
<TabNavigation onTabPress={handleTabPress} selectedTab={selectedTab} />
<View style={{ padding: 20 }}>
{selectedTab === 'environmental' && (
<EnvironmentalRecordsList records={environmentalRecords} />
)}
{selectedTab === 'biological' && (
<BiologicalRecordsList records={biologicalRecords} />
)}
</View>
</ScrollView>
);
function createRecordLists() {
const EnvironmentalRecordsList: React.FC<{ records: NatureRecord[]; }> = ({ records }) => (
<View>
{records.map((record, index) => (
<View key={index} style={styles.recordContainer}>
<Text style={styles.reviewText}>{record.review}</Text>
<Image source={{ uri: record.photo }} style={styles.photo} />
{record.sliders.map((slider: { name: string; value: number; }, sliderIndex: number) => (
<Text key={sliderIndex} style={styles.sliderText}>{slider.name}: {slider.value}</Text>
))}
</View>
))}
</View>
);
const BiologicalRecordsList: React.FC<{ records: NatureRecord[]; }> = ({ records }) => (
<View>
{records.map((record, index) => (
<View key={index} style={styles.recordContainer}>
<Text style={styles.reviewText}>{record.review}</Text>
<Image source={{ uri: record.photo }} style={styles.photo} />
{record.sliders.map((slider: { name: string; value: number; }, sliderIndex: number) => (
<Text key={sliderIndex} style={styles.sliderText}>{slider.name}: {slider.value}</Text>
))}
</View>
))}
</View>
);
return { EnvironmentalRecordsList, BiologicalRecordsList };
}
};
const styles = StyleSheet.create({
recordContainer: {
marginBottom: 20,
padding: 10,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 5,
},
reviewText: {
fontSize: 16,
fontWeight: 'bold',
},
photo: {
width: 100,
height: 100,
marginVertical: 10,
},
sliderText: {
fontSize: 14,
},
});
export default RecordStorage;
import React, { useState, useMemo, useCallback } from 'react';
import { ScrollView, View, TextInput, StyleSheet, Alert, Text } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useNatureRecord } from '@/RecordLogic/RecordContext';
import SaveButton from '@/RecordComponents/SaveButton';
import ReviewComponentStyle from '@/RecordStyle/ReviewComponentStyle';
import TopBanner from '@/RecordComponents/TopBanner';
import SliderComponent from '@/RecordComponents/SliderComponent';
import PhotoDisplay from '@/RecordComponents/PhotoDisplay';
import BigQuestionStyle from '@/RecordStyle/BigQuestionStyle';
const initialSliders = [
{ name: '탐험한 곳은 얼마나 아름다웁니까?', value: 50 },
{ name: '다른사람에게 이곳을 탐험하는 걸 추천하고 싶으면 어느정도?', value: 50 },
{ name: '탐험하는 동안 얼마나 즐거웠나요?', value: 50 },
{ name: '탐험하는 동안 얼마나 가치있었나요?', value: 50 },
{ name: '탐험 난이도는 어느정도 였나요?', value: 50 },
];
const labels = [
{ text: '아름다움' },
{ text: '추천 수준' },
{ text: '즐거움' },
{ text: '가치' },
{ text: '난이도' },
];
const NatureRecordForm: React.FC = () => {
const [review, setReview] = useState('');
const [photo, setPhoto] = useState('');
const [sliders, setSliders] = useState(initialSliders);
const [inputHeight, setInputHeight] = useState(40); // Initial height for the TextInput
const navigation = useNavigation<any>();
const { addEnvironmentalRecord } = useNatureRecord();
const memoizedSliders = useMemo(() => sliders, [sliders]);
const handleAddRecord = useCallback(async () => {
try {
const newRecord = {
id: Date.now(), // Generate a unique id
review,
photo,
sliders: memoizedSliders,
timestamp: new Date().toISOString(), // Ensure timestamp is in a consistent format
};
await addEnvironmentalRecord(newRecord);
Alert.alert('Record saved successfully!');
navigation.navigate('RecordStorage'); // Navigate to RecordStorage page
} catch (error) {
console.error('Error saving record: ', error);
Alert.alert('Failed to save record.');
}
}, [review, photo, memoizedSliders, addEnvironmentalRecord, navigation]);
const handleSave = useCallback((data: { name: string; value: number }[]) => {
setSliders(data);
}, []);
const handleClosePress = useCallback(() => {
Alert.alert(
"뒤로 넘어가면 기록이 삭제되는데 괜찮습니까?",
"",
[
{ text: "취소", style: "cancel" },
{ text: "확인", onPress: () => console.log("Close confirmed") }
]
);
}, []);
return (
<ScrollView style={{ flex: 1, padding: 0, margin: 0 }}>
<TopBanner
source={require('C:\\app\\my-app\\NatureImg\\close.svg')}
text="자연환경 도감 등록"
onClosePress={handleClosePress}
/>
{photo ? <PhotoDisplay photoUri={photo} /> : null}
<View style={BigQuestionStyle.container}>
<Text style={BigQuestionStyle.howWouldYou}>탐험을 하셨군요!{'\n'}{'\n'}다음을 평가해주세요!</Text>
</View>
<SliderComponent initialSliders={memoizedSliders} onSave={handleSave} labels={labels} />
<View style={{ marginBottom: 20 }} />
<View style={ReviewComponentStyle.inputContainer}>
<View style={ReviewComponentStyle.inputWrapper}>
<TextInput
placeholder="한 줄 입력"
value={review}
onChangeText={setReview}
style={[ReviewComponentStyle.input, { height: inputHeight }]}
multiline
onContentSizeChange={(e) => setInputHeight(e.nativeEvent.contentSize.height)}
/>
</View>
</View>
<TextInput
placeholder="사진 URL 입력"
value={photo}
onChangeText={setPhoto}
style={ReviewComponentStyle.photo}
/>
<View style={styles.buttonWrapper}>
<SaveButton title="기록 저장" onPress={handleAddRecord} />
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
buttonWrapper: {
marginVertical: 20,
alignItems: 'center',
},
});
export default React.memo(NatureRecordForm);
import React, { createContext, useState, useContext, ReactNode, useEffect } from "react";
import { collection, addDoc, getDocs } from 'firebase/firestore';
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { db, storage } from '@/firebase';
interface NatureRecord {
id: number;
photo: string;
review: string;
sliders: { name: string; value: number }[];
}
interface NatureRecordContextProps {
environmentalRecords: NatureRecord[];
biologicalRecords: NatureRecord[];
addEnvironmentalRecord: (record: NatureRecord) => Promise<void>;
addBiologicalRecord: (record: NatureRecord) => Promise<void>;
fetchRecords: () => Promise<void>;
}
const NatureRecordContext = createContext<NatureRecordContextProps | undefined>(undefined);
export const NatureRecordProvider = ({ children }: { children: ReactNode }) => {
console.log("Provider rendering"); // Add this line to check if the provider is rendering
const [environmentalRecords, setEnvironmentalRecords] = useState<NatureRecord[]>([]);
const [biologicalRecords, setBiologicalRecords] = useState<NatureRecord[]>([]);
const addEnvironmentalRecord = async (record: NatureRecord) => {
console.log("Adding environmental record", record); // Debugging log
try {
let photoURL = '';
if (record.photo) {
const storageReference = ref(storage, `photos/${Date.now()}_${record.photo}`);
const response = await fetch(record.photo);
const blob = await response.blob();
await uploadBytes(storageReference, blob);
photoURL = await getDownloadURL(storageReference);
}
const newRecord = {
...record,
photo: photoURL,
timestamp: new Date().toISOString(),
};
await addDoc(collection(db, 'environmentalRecords'), newRecord);
setEnvironmentalRecords([...environmentalRecords, newRecord]);
} catch (error) {
console.error('Error saving record to Firebase: ', error);
}
};
const addBiologicalRecord = async (record: NatureRecord) => {
console.log("Adding biological record", record); // Debugging log
try {
let photoURL = '';
if (record.photo) {
const storageReference = ref(storage, `photos/${Date.now()}_${record.photo}`);
const response = await fetch(record.photo);
const blob = await response.blob();
await uploadBytes(storageReference, blob);
photoURL = await getDownloadURL(storageReference);
}
const newRecord = {
...record,
photo: photoURL,
timestamp: new Date().toISOString(),
};
await addDoc(collection(db, 'biologicalRecords'), newRecord);
setBiologicalRecords([...biologicalRecords, newRecord]);
} catch (error) {
console.error('Error saving record to Firebase: ', error);
}
};
const fetchRecords = async () => {
console.log("Fetching records"); // Debugging log
try {
const environmentalSnapshot = await getDocs(collection(db, 'environmentalRecords'));
const biologicalSnapshot = await getDocs(collection(db, 'biologicalRecords'));
const environmentalData = environmentalSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) as unknown as NatureRecord[];
const biologicalData = biologicalSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) as unknown as NatureRecord[];
setEnvironmentalRecords(environmentalData);
setBiologicalRecords(biologicalData);
} catch (error) {
console.error('Error fetching records from Firebase: ', error);
}
};
useEffect(() => {
fetchRecords();
}, []);
return (
<NatureRecordContext.Provider value={{ environmentalRecords, biologicalRecords, addEnvironmentalRecord, addBiologicalRecord, fetchRecords }}>
{children}
</NatureRecordContext.Provider>
);
};
export const useNatureRecord = () => {
const context = useContext(NatureRecordContext);
if (!context) {
throw new Error("useNatureRecord must be used within a NatureRecordProvider");
}
return context;
};
故障排除步骤:
问题:
任何有关解决此错误的指导将不胜感激!
我希望 NatureRecordProvider 上下文向导航堆栈中的所有组件提供数据,因为我将导航堆栈包装在 NatureRecordProvider 中。我预计 useNatureRecord 可以在 NatureRecordForm 和 RecordStorage 中正常工作,不会出现错误。然而,相反,我遇到了错误消息:“useNatureRecord 必须在 NatureRecordProvider 中使用。”
您可以考虑以下几点:
首先,您的导入语句不同:
自然记录表格:
import { useNatureRecord } from '@/RecordLogic/RecordContext';
记录存储:import { useNatureRecord } from '../../RecordLogic/RecordContext';
确保它们在同一上下文中指向相同的
useNatureRecord
函数。
其次,尝试将 Navigator 和 Provider 移至
App.tsx
以确保所有组件均已包装并重新验证。这样您还可以调试是否有任何组件缺少 ProviderWrapper
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import RecordNavigation from './RecordNavigation';
import { NatureRecordProvider } from './RecordContext';
const App = () => {
return (
<NatureRecordProvider>
<NavigationContainer>
<RecordNavigation />
</NavigationContainer>
</NatureRecordProvider>
);
};
export default App;