'Clear form input fields after submit it to firestore?

I'm trying to create a simple CRUD application with react and Firestore (new to both). I never used react with db, so previously I would simply set the state of title and content to "" after submit, but that's create a loop with Firestore onSnapshot. I could wrap it in form and use preventDefault(), but (possibly my mistake) it created strange outcome unless I left behind async. I'm thinking about this issue for a few days and yet to find the right answer to it. Maybe I could prevent Firestore to accept and save empty strings? That cant be a reasonable answer as it would create an extra loop front to back for no other reason than my shortcoming. I think the answer should be some sort of a conditionally state setting, but that's just a guess again. Now I`ve watched quite a few hours a tutorials on youtube, but they all leave out this issue, which I think its a bit silly. I would like to find the answer to it as even though I might could come up with something that will work, but I really like to see the common use case of it which defo will be more elegant than my trial and error solution. Thank you in advance!

function App() {

  const [newTitle, setNewTitle] = useState("")
  const [newContent, setNewContent] = useState("")
  const [users, setUsers] = useState([]);

  const usersCollectionRef = collection(db, "users")

  useEffect(
    () =>
      onSnapshot(collection(db, "users"), (snapshot) =>
        setUsers(snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })))
      ),
    []
  );

  const createEntry = async () => {
        await addDoc(usersCollectionRef, { Title: newTitle, Content: newContent })
  }

  return (

    <div>
        <input type="text" placeholder="Title" onChange={event => { setNewTitle(event.target.value)} }/>
        <textarea type="text" placeholder="Content" onChange={event => { setNewContent(event.target.value) } }/> 
        <button onClick={createEntry}> Add </button>

       {users.map((user) => {
        return (
          <div className="note">
            <h1> {user.Title} </h1>
            <p>{user.Content}</p>
          </div>
        )
      })}
    </div>
  );
}


Solution 1:[1]

Took me forever to figure out what Im doing wrong, but seems like it finally I overcome it. Now took my answer with a pinch of salt as Im hoping it will be helpful for you, but might not all my "theories" will be 100% correct. (PS: changed users to notes as it makes a lot more sense)

While I was studying I remembered a project which exactly the same as this, but without firestore backend. I reused that project and done a lot of trial and error to find the solution.

I did remember that on that project it was broken down to components and we passed back the relevant info to the app.jsx. But while i was thinking i realised that we actually done more than that. I think might be the issue that here I used 3 different states.

const [newTitle, setNewTitle] = useState("")
const [newContent, setNewContent] = useState("")
const [users, setUsers] = useState([]);

Now its all nice and functional, but uncontrolled I believe? Now what we have done in the previous project that we actually stored the individual user entries in and object and passed back the id`s to the app.js. While we were doing it we reset the state of the object fields to "".

 const [note, setNote] = useState({
       title:"",
       content:""
    });

I think might be the reason for this to work now that we have objects in an array, not individual strings seemingly not related to each other. ? Its a guess from my side.

app.jsx

const [notes, setNotes] = useState([]);

const usersCollectionRef = collection(db, "notes")

const createEntry = async (note) => {
        await addDoc(usersCollectionRef, { title: note.title, content: note.content })
  }

 return (
    <div>      
    <CreateArea onAdd={createEntry} />
      {notes.map((noteItem) => {
        return (
          <Note
            key={noteItem.id}
            id={noteItem.id}
            title={noteItem.title}
            content={noteItem.content}
            onDelete={deleteNote}
          />
        );
      })}
    </div>
  );
}

createArea.jsx

function CreateArea(props) {
  const [note, setNote] = useState({
    title: "",
    content: ""
  });

  function handleChange(event) {
    const { name, value } = event.target;

    setNote(prevNote => {
      return {
        ...prevNote,
        [name]: value
      };
    });
  }

  function submitNote(event) {
    props.onAdd(note);
    setNote({
      title: "",
      content: ""
    });
    event.preventDefault();
  }

  return (
    <div className="create-area">
      <form>
        <input
          name="title"
          onChange={handleChange}
          value={note.title}
          placeholder="Title"
        />
        <textarea
          name="content"
          onChange={handleChange}
          value={note.content}
          placeholder="Take a note..."
          rows="3"
        />
        <button onClick={submitNote}>Add</button>
      </form>
    </div>
  );
}

By doing this I use the spread operator to handle the value changes and until it is submitted it wont be reset to "". I think the issue might have been that the i would have needed 3 state to do this initially.

  • first state: ""
  • second state: handles the input changes
  • third state: submitting and reset value to ""

Doing this with my original states wouldnt be logically possible i think so one extra step needed. On submission it sends back the note where we have access to its fields to save it. Its probably not a biggie for some, but Im happy that i find solution on my own, although it took me longer than i expected. I hope it can be helpful for someone. Please note that all the "ideas" why it didn`t work are ideas only. If you have anything to add or correct me you are more than welcome to! Thank you!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 AndrasTheNewBoi