You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Formik+Firestore开发Ins克隆版遇权限不足及空值报错

两类异常的根因与修复方案

错误根因汇总

1. 快照监听权限不足报错(Missing or insufficient permissions)

  • 集合名拼写错误:查询用户信息时调用的是db.collection('user')(单数命名),但Firestore安全规则仅配置了复数路径/users/{userId}的访问权限,未匹配到规则的请求会被Firestore默认拒绝。
  • 查询条件传值错误:where('owner_uid', '==', 'user.uid')把变量user.uid写成了固定字符串,即使集合名正确也无法查询到目标用户文档。
  • onSnapshot未绑定错误处理回调,监听抛出的异常直接冒泡到全局。

2. 提交表单时空对象报错(null is not an object)

  • 状态初始值问题:currentLoggedInUser初始值为null,用户信息未完成加载时点击提交,访问currentLoggedInUser.username会直接触发空指针错误。
  • 前置查询失败:由于上述用户信息查询的拼写、传值错误,用户数据从未成功拉取,currentLoggedInUser始终为null,提交必然报错。
  • 副作用未清理:useEffect中注册的快照监听没有在组件卸载时取消,会造成内存泄漏。
  • 上传逻辑未做空值校验,也未捕获Promise异常,错误直接触发Formik的未处理错误警告。

分步修复方案

第一步:修正用户信息查询逻辑

  • 统一集合名为users,和安全规则、上传逻辑的命名保持一致
  • 修正where条件的传值,使用实际的用户UID变量而非字符串
  • 给快照监听添加错误回调,在useEffect返回值中调用unsubscribe清理监听
  • 新增加载态,用户信息未加载完成时禁用提交按钮

第二步:修正表单提交逻辑

  • 提交前校验currentLoggedInUser是否已赋值,未加载完成直接终止提交
  • 合并图片输入框的重复事件绑定,同时更新缩略图预览和表单值
  • 给上传Promise添加catch捕获异常,避免未处理错误冒泡
  • 确保上传成功后再触发导航返回

第三步:确认Firestore安全规则配置

现有规则已覆盖根级users集合和所有子级posts集合的认证访问权限,只要修正代码中的集合名拼写即可正常访问,无需额外调整规则。基础可用的规则参考如下:

rules_version = '2';
service cloud.firestore {
    match /databases/{database}/documents {
        function userIsAuthenticated() {
            return request.auth != null;
        }
        // 匹配所有层级下的posts集合
        match /{path=**}/posts/{postId} {
            allow read, write: if userIsAuthenticated();
        }
        // 匹配根级用户集合
        match /users/{userId} {
            allow read, write: if userIsAuthenticated();
        }
    }
}

修正后的完整业务代码

import { View, Text, Image, TextInput, Button, ActivityIndicator } from 'react-native'
import React, { useState, useEffect } from 'react'
import * as Yup from 'yup'
import { Formik } from 'formik'
import { Divider } from 'react-native-elements'
import validUrl from 'valid-url'
import {db, firebase} from '../../firebase'

const PLACEHOLDER_IMG = 'https://pacificpatiostructures.com/wp-content/uploads/2016/06/import_placeholder.png'

const uploadPostSchema = Yup.object().shape({
    imageUrl: Yup.string().url().required('A URL is required'),
    caption: Yup.string().max(2200, 'Caption has reached the character limit.')
})

const FormikPostUploader = ({ navigation }) => {
    const [thumbnailUrl, setThumbnailUrl] = useState(PLACEHOLDER_IMG)
    const [currentLoggedInUser, setCurrentLoggedInUser] = useState(null)
    const [loadingUser, setLoadingUser] = useState(true)
    
    const getUsername = () => {
        const user = firebase.auth().currentUser
        // 修正集合名拼写、where条件传值错误
        const unsubscribe = db
        .collection('users')
        .where('owner_uid', '==', user.uid).limit(1).onSnapshot(
            snapshot => {
                snapshot.docs.map(doc => {
                    setCurrentLoggedInUser({
                        username: doc.data().username,
                        profilePicture: doc.data().profile_picture,
                    })
                    setLoadingUser(false)
                })
            },
            // 新增监听错误回调
            (error) => {
                console.log('用户信息监听失败:', error)
                setLoadingUser(false)
            }
        )
        return unsubscribe
    }

    useEffect(() => {
        const unsubscribe = getUsername()
        // 组件卸载时取消监听,避免内存泄漏
        return () => unsubscribe()
    }, [])

    const uploadPostToFirebase = async (imageUrl, caption) => {
        // 新增空值校验,用户信息未加载完成直接终止提交
        if (!currentLoggedInUser || !firebase.auth().currentUser) return
        try {
            await db
                .collection('users')
                .doc(firebase.auth().currentUser.email).collection('posts')
                .add({
                    imageUrl: imageUrl,
                    user: currentLoggedInUser.username,
                    profile_picture: currentLoggedInUser.profilePicture,
                    owner_uid: firebase.auth().currentUser.uid,
                    caption: caption,
                    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                    likes: 0,
                    likes_by_users: [],
                    comments: [],
                })
            navigation.goBack()
        } catch (error) {
            console.log('帖子上传失败:', error)
        }
    }

  return (
      <Formik
        initialValues={{caption: '', imageUrl: ''}}
        onSubmit={values => {
            uploadPostToFirebase(values.imageUrl, values.caption)
        }}
        validationSchema={uploadPostSchema}
        validateOnMount={true}
        >
            {({ 
                handleBlur, 
                handleChange, 
                handleSubmit, 
                values, 
                errors, 
                isValid 
            }) => (
                <>
                <View 
                    style={{ 
                        margin: 20, 
                        justifyContent: 'space-between', 
                        flexDirection: 'row', 
                    }}>
                    <Image source={{ uri: validUrl.isUri(thumbnailUrl) ? thumbnailUrl : PLACEHOLDER_IMG}} 
                            style={{ width: 100, height: 100 }}/>

                    <View style={{ flex: 1, marginLeft: 12 }}>
                        <TextInput 
                            style={{ color: 'white', fontSize: 20 }}
                            placeholder='Write a caption...' 
                            placeholderTextColor='gray'
                            multiline={true}
                            onChangeText={handleChange('caption')}
                            onBlur={handleBlur('caption')}
                            value={values.caption}
                        />
                    </View>
                </View>
                <Divider width = {0.2} orientation='vertical' />
                <TextInput 
                    style={{ color: 'white', fontSize: 18 }}
                    placeholder='Enter Image Url' 
                    placeholderTextColor='gray'
                    // 合并重复事件绑定,同时更新缩略图和表单值
                    onChangeText={(text) => {
                        setThumbnailUrl(text)
                        handleChange('imageUrl')(text)
                    }}
                    onBlur={handleBlur('imageUrl')}
                    value={values.imageUrl}
                />
                {errors.imageUrl &&(
                    <Text style={{ fontSize: 10, color: 'red' }}>
                        {errors.imageUrl}
                    </Text>
                )}
                {/* 加载中或表单无效时禁用按钮 */}
                <Button 
                    onPress={handleSubmit} 
                    title={loadingUser ? '加载中...' : 'Share'} 
                    disabled={!isValid || loadingUser}
                />
                </>
            )}
      </Formik>
  )
}

export default FormikPostUploader

内容的提问来源于stack exchange,提问作者R3CURSION

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 12:42:17