未捕获错误:useNatureRecord 必须在 React Native TypeScript 应用程序的 NatureRecordProvider 中使用

问题描述 投票:0回答:1

我正在使用 TypeScript 开发 React Native 应用程序并使用 Firebase 进行数据存储。在我的应用程序中,我有一个上下文提供程序 (NatureRecordProvider),它应该包装两个主屏幕:NatureRecordForm 和 RecordStorage。但是,当我导航到这些屏幕时,我的网络服务器中遇到以下错误:

错误信息:

在此输入图片描述

完整的错误跟踪表明 useNatureRecord 正在 NatureRecordProvider 之外使用,尽管我相信我已经使用提供程序包装了必要的组件。

这是我的设置摘要:

  1. 上下文设置:
  • NatureRecordProvider 定义在 RecordContext.tsx 中,提供了多个函数(addEnvironmentalRecord、addBiologicalRecord 等)来管理记录。
  • useNatureRecord 是一个用于访问上下文的自定义挂钩。
  1. 导航配置:
  • RecordNavigation 组件用 NatureRecordProvider 包装 NavigationContainer。
  1. 组件使用:
  • NatureRecordForm 和 RecordStorage 均通过 RecordNavigation 访问。

代码示例:

  1. 记录导航.tsx:
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>
  );
};
  1. 记录存储.tsx
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;
  1. NatureRecordForm.tsx
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);
  1. RecordContext.tsx
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 包装了 NavigationContainer。
  • 检查 useNatureRecord 仅在 RecordNavigation 下的组件内调用。

问题:

  • 尽管 NatureRecordProvider 似乎包装了必要的组件,但为什么我会收到此错误?
  • 这可能与提供程序内的导航结构或异步数据加载有关吗?

任何有关解决此错误的指导将不胜感激!

我希望 NatureRecordProvider 上下文向导航堆栈中的所有组件提供数据,因为我将导航堆栈包装在 NatureRecordProvider 中。我预计 useNatureRecord 可以在 NatureRecordForm 和 RecordStorage 中正常工作,不会出现错误。然而,相反,我遇到了错误消息:“useNatureRecord 必须在 NatureRecordProvider 中使用。”

typescript firebase react-native error-handling react-context
1个回答
0
投票

您可以考虑以下几点:

首先,您的导入语句不同:

自然记录表格:

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;
© www.soinside.com 2019 - 2024. All rights reserved.