TribeOfOne
TribeOfOne

Reputation: 173

Firestore doc field update in react app giving TypeError unless I refresh first

I have a react app which allows creation of lists for logged in users (groceries, todo, etc) and stores data on firebase.firestore. It's still in development using localhost. If I create a new list, add items to it and immediately edit the item description, I get a TypeError and item description doesn't update in app or firestore. If I pick a different list to display, then click back to new list or refresh the browser before I edit the item description, everything works fine. It just doesn't work if I specifically create new list, add items to it, and immediately try to edit the item description, even though I check firestore before submitting change and the new list and new items are shown to exist.

Any idea why the await checkDoc in the handleSubmit isn't working unless I refresh app in browser first?

I included firestore screenshots before and after app refresh in case it matters. They seem identical to me.

Github repo is branch edit-item github repo

error showing in console when I update item description without refreshing:

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)

EditItem componant with handleSubmit function giving error unless I refresh first:

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;

Dashboard component (parent to 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 screenshot before refreshing app (I just created "test list" and two items and updating item desc doesn't work) enter image description here

firestore screenshot after refreshing app (updating item description works) enter image description here

Upvotes: 1

Views: 140

Answers (1)

TribeOfOne
TribeOfOne

Reputation: 173

I figured it out...

The id of newly created item which I am using to update the doc is a number. But the id on firestore is a string, not a number.

changing the handleSubmit callback from .doc(editItem.id) to this fixed it:

.doc(`${editItem.id}`)

Upvotes: 1

Related Questions