除非我先刷新,否则 React 应用程序中的 Firestore 文档字段更新会给出 TypeError

Posted

技术标签:

【中文标题】除非我先刷新,否则 React 应用程序中的 Firestore 文档字段更新会给出 TypeError【英文标题】:Firestore doc field update in react app giving TypeError unless I refresh first 【发布时间】:2022-01-17 00:01:57 【问题描述】:

我有一个 react 应用程序,它允许为登录用户(杂货、待办事项等)创建列表并将数据存储在 firebase.firestore 上。它仍在使用 localhost 进行开发。如果我创建一个新列表,向其中添加项目并立即编辑项目描述,我会收到 TypeError 并且项目描述不会在应用程序或 Firestore 中更新。如果我选择要显示的不同列表,然后在编辑项目描述之前单击返回新列表或刷新浏览器,一切正常。如果我专门创建新列表、向其中添加项目并立即尝试编辑项目描述,它只是不起作用,即使我在提交更改之前检查了 firestore 并且新列表和新项目显示存在。

知道为什么除非我先在浏览器中刷新应用程序,否则 handleSubmit 中的 await checkDoc 不起作用吗?

我在应用刷新前后添加了 Firestore 屏幕截图,以防万一。他们看起来和我一模一样。

Github repo 是分支编辑项 github repo

当我更新项目描述而不刷新时,控制台中显示错误:

TypeError: u.indexOf is not a function
    at Function.e.ot (prebuilt-67479dbf-318e5a2c.js:878)
    at Bs (prebuilt-67479dbf-318e5a2c.js:14560)
    at e.doc (prebuilt-67479dbf-318e5a2c.js:18281)
    at handleSubmit (EditItem.js:32)
    at htmlUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070)
    at executeDispatch (react-dom.development.js:8243)
    at processDispatchQueueItemsInOrder (react-dom.development.js:8275)
    at processDispatchQueue (react-dom.development.js:8288)
    at dispatchEventsForPlugins (react-dom.development.js:8299)
    at react-dom.development.js:8508
    at batchedEventUpdates$1 (react-dom.development.js:22396)
    at batchedEventUpdates (react-dom.development.js:3745)
    at dispatchEventForPluginEventSystem (react-dom.development.js:8507)
    at attemptToDispatchEvent (react-dom.development.js:6005)
    at dispatchEvent (react-dom.development.js:5924)
    at unstable_runWithPriority (scheduler.development.js:646)
    at runWithPriority$1 (react-dom.development.js:11276)
    at discreteUpdates$1 (react-dom.development.js:22413)
    at discreteUpdates (react-dom.development.js:3756)
    at dispatchDiscreteEvent (react-dom.development.js:5889)

除非我先刷新,否则带有 handleSubmit 函数的 EditItem 组件会出错:

import React,  useState  from 'react';
import  db  from '../../hooks/useAuth';

import 
  Button,
  Input,
  Center,
  FormControl,
  Flex,
  Heading,
 from '@chakra-ui/react';

const EditItem = (
  user,
  items,
  setItems,
  setEditItem,
  currentList,
  editItem,
  themeObj,
) => 
  const checkDoc = db.collection('users').doc(user.uid);

  const [newDesc, setNewDesc] = useState('');
  const handleSubmit = async e => 
    e.preventDefault();
    try 
      console.log(editItem);
      console.log(newDesc);
      console.log(currentList);
      console.log(editItem.id);
      await checkDoc
        .collection(currentList)
        .doc(editItem.id)
        .update( desc: newDesc );

      const editedList = items.map(item =>
        item.id === editItem.id ?  ...item, desc: newDesc  : item
      );
      setItems(editedList);
      console.log(editedList);
      setNewDesc(`$editItem.desc`);

      setEditItem(null);
     catch (err) 
      console.log(err);
     finally 
    
  ;

  return (
    <Flex w="100%" grow="1" direction="column" p=6>
      <Center
        mb="1rem"
        borderRadius="lg"
        p=3
        bg=themeObj.bg
        color=themeObj.color
      >
        <Heading size="md">Edit Item Description</Heading>
      </Center>
      <Center
        mb="1rem"
        borderRadius="lg"
        p=3
        bg=themeObj.bgItem
        color=themeObj.colorItem
      >
        newDesc.length ? newDesc : editItem.desc
      </Center>
      <form
        label="New Item Description"
        onSubmit=handleSubmit
        style= width: '100%' 
      >
        <FormControl>
          <Input
            required
            variant="outline"
            autoFocus
            // ref=inputRef
            type="text"
            id="newDesc"
            placeholder="New Item Description"
            required
            value=newDesc
            onChange=e => setNewDesc(e.target.value)
          />

          <Button
            variant="solid"
            mt=4
            type="submit"
            aria-label="Rename List"
            color="white"
            _hover=
              background: `$themeObj.checkScheme`,
            
            bg="black"
          >
            Update
          </Button>
          <Button
            variant="solid"
            mt=4
            type="button"
            onClick=() => 
              setEditItem(null);
              setNewDesc(`$editItem.desc`);
            
            aria-label="cancel"
            color="white"
            _hover=
              background: `$themeObj.checkScheme`,
            
            bg="red"
          >
            Cancel
          </Button>
        </FormControl>
      </form>
    </Flex>
  );
;

export default EditItem;

仪表板组件(EditItem 的父级):

import React,  useState, useEffect  from 'react';
import  InputGroup, Stack, Flex  from '@chakra-ui/react';
import firebase from 'firebase/app';
import EditItem from '../iList/EditItem';
import Loader from '../iList/Loader';
import AddItem from '../iList/AddItem';
import SearchItem from '../iList/SearchItem';
import Content from '../iList/Content';
import Footer from '../iList/Footer';
import  useAuth, db  from '../../hooks/useAuth';
import 'firebase/firestore';

const Dashboard = (
  setAppTheme,
  loaderLoading,
  setLoaderLoading,
  setIsLoading,
  isLoading,
  fetchError,
  setFetchError,
  themeObj,
  currentList,
  setCurrentList,
) => 
  console.log(currentList);
  const  user  = useAuth();
  const [items, setItems] = useState([]);
  const [search, setSearch] = useState('');
  const [editItem, setEditItem] = useState(null);

  const [newItem, setNewItem] = useState('');

  const checkDoc = db.collection('users').doc(user.uid);

  const itemsCollection = db
    .collection('users')
    .doc(user.uid)
    .collection(currentList);

  useEffect(() => 
    checkIfInitialized();
    const getUserPrefs = async () => 
      try 
        const userList = await checkDoc.get();
        setCurrentList(userList.data().currentlist);
        setAppTheme(userList.data().currenttheme);

        setFetchError(null);
       catch (err) 
        setFetchError(err.message);

        console.log(err.message);
       finally 
        setLoaderLoading(false);
        setIsLoading(false);
      
    ;
    getUserPrefs();
  , []);

  useEffect(() => 

    const getItems = async () => 
      try 
        const data = await itemsCollection.get();

        const listItems = data.docs.map(doc => (
          ...doc.data(),
          id: doc.id,
        ));
        setItems(listItems);
        setFetchError(null);
       catch (err) 
        setFetchError(err.message);
       finally 
        setLoaderLoading(false);
      
    ;
    getItems();
  , [currentList]);

  const addItem = async item => 
    const id = items.length ? Number(items[items.length - 1].id) + 1 : 1;
    console.log(id);
    const newItemDate = new Date();
    const dateStr = `$
      newItemDate.getMonth() + 1
    /$newItemDate.getDate()/$newItemDate.getFullYear()`;

    const myNewItem = 
      id: id,
      checked: false,
      desc: item,
      date: dateStr,
    ;

    const listItems = [...items, myNewItem];
    setItems(listItems);
    const addedDoc = db
      .collection('users')
      .doc(user.uid)
      .collection(currentList)
      .doc(`$myNewItem.id`);
    await addedDoc
      .set( desc: myNewItem.desc, checked: false, date: dateStr )
      .then(() => 
        console.log('Document successfully written!');
      )
      .catch(error => 
        console.error('Error writing document: ', error);
      );
  ;

  const handleCheck = async id => 
    const listItems = items.map(item =>
      item.id === id ?  ...item, checked: !item.checked  : item
    );
    setItems(listItems);
    const myItem = items.filter(item => item.id === id);
    console.log(myItem);

    const updatedDoc = db
      .collection('users')
      .doc(user.uid)
      .collection(currentList)
      .doc(`$id`);
    console.log('here');
    await updatedDoc
      .update(
        checked: !myItem[0].checked,
      )
      .then(() => 
        console.log('Document successfully updated!');
      )
      .catch(error => 
        // The document probably doesn't exist.
        console.error('Error updating document: ', error);
      );
  ;
  const handleDelete = async id => 
    const listItems = items.filter(item => item.id !== id);
    setItems(listItems);
    const deletedDoc = db
      .collection('users')
      .doc(user.uid)
      .collection(currentList)
      .doc(`$id`);
    await deletedDoc
      .delete()
      .then(() => 
        console.log('Document successfully deleted!');
      )
      .catch(error => 
        console.error('Error removing document: ', error);
      );
  ;

  const handleSubmit = e => 
    e.preventDefault();
    if (!newItem) return;
    addItem(newItem);
    setNewItem('');
  ;
  const checkIfInitialized = () => 
    const docRef = db.collection('users').doc(user.uid);

    docRef
      .get()
      .then(doc => 
        if (doc.exists) 
          console.log('Document data:', doc.data());
         else 
          // doc.data() will be undefined in this case
          console.log('No such document!');
          initializeUserDb();
        
      )
      .catch(error => 
        console.log('Error getting document:', error);
      );
  ;

  const initializeUserDb = async () => 
    const firstEntry = db.collection('users').doc(user.uid);

    await firstEntry
      .set(
        currentlist: currentList,
        mylists: firebase.firestore.FieldValue.arrayUnion('My List'),
        currenttheme: 'default',
        email: user.email,
      )
      .then(() => 
        console.log('currentlist successfully written!');
      )
      .catch(error => 
        console.error('Error writing document: ', error);
      );
  ;

  return (
    <>
      editItem && (
        <EditItem
          items=items
          setItems=setItems
          currentList=currentList
          user=user
          editItem=editItem
          setEditItem=setEditItem
          themeObj=themeObj
        />
      )

      !editItem && (
        <>
          <Stack mb=3 w="100%" p=3>
            <InputGroup>
              <AddItem
                themeObj=themeObj
                newItem=newItem
                setNewItem=setNewItem
                handleSubmit=handleSubmit
              />
            </InputGroup>
            <InputGroup>
              <SearchItem
                themeObj=themeObj
                search=search
                setSearch=setSearch
              />
            </InputGroup>
          </Stack>
          <Flex
            w="100%"
            flexDirection="column"
            flexGrow="1"
            justifyContent="flex-start"
            align-items="center"
            overflowY="auto"
          >
            (isLoading || loaderLoading) && <Loader />

            fetchError && (
              <p style= color: 'red' >`Error: $fetchError`</p>
            )

            !fetchError && !loaderLoading && !isLoading && (
              <Content
                setEditItem=setEditItem
                themeObj=themeObj
                items=items.filter(item =>
                  item.desc.toLowerCase().includes(search.toLowerCase())
                )
                handleDelete=handleDelete
                handleCheck=handleCheck
              />
            )
          </Flex>
        </>
      )

      <Footer bg=themeObj.bg color=themeObj.color length=items.length />
    </>
  );
;

export default Dashboard;

刷新应用程序之前的firestore屏幕截图(我刚刚创建了“测试列表”和两个项目,更新项目描述不起作用)

刷新应用后的firestore屏幕截图(更新项目描述有效)

【问题讨论】:

【参考方案1】:

我想通了……

我用来更新文档的新创建项目的 id 是一个数字。但是firestore上的id是字符串,不是数字。

将 handleSubmit 回调从 .doc(editItem.id) 更改为修复它:

.doc(`$editItem.id`)

【讨论】:

以上是关于除非我先刷新,否则 React 应用程序中的 Firestore 文档字段更新会给出 TypeError的主要内容,如果未能解决你的问题,请参考以下文章

除非刷新页面,否则React loadable无法在每个新构建上加载组件

除非我刷新,否则 jQuery mobile 中的 JavaScript 不起作用

SmartEdit:除非刷新页面,否则嵌套 CMS 组件中的更改不会反映

添加“onmouseenter”(悬停)音频效果,但除非我先点击,否则它不会在 chrome 中播放?

除非刷新页面,否则 Jquery Mobile Web 应用程序无法正常工作

除非重新加载页面,否则 React 应用程序路由不会加载内容