1

I am having a really bizarre bug. Occasionally, when I submit the form the "userId" cookie is not updated. I think I have isolated the problem to something to do with

return user.set({
    roomId: roomId,
    name: name
  }).then(() => alert("finished writing"))

as when the cookie isn't updated the "finished writing" alert is never called. I think it is something to with a promise not being resolved, and that taking up the thread so the .then is never run (and consequently the .then with the cookie update is never run). However, I don't really understand promises and I can't find what might be holding it up. If anyone could help me out or point out the bug it would be really appreciated. Here is the code:

function GamePage() {
  const {roomId} = useParams();
  const db = firebase.firestore();
  let history = useHistory();
  const [userIdExistsButWrongRoom, setUserIdExistsButWrongRoom] = useState(true);
  const userId = cookies.get('userId');
  useEffect(() => {
    if(!(typeof(userId)==="undefined")){
    db.collection("users").doc(userId).get().then(function(doc){
      if(doc.data().roomId === roomId){
          setUserIdExistsButWrongRoom(false);
        } 
      });
    }
  }, [db,roomId,userId]);

  if(cookies.get('roomId')===roomId){
    if(typeof(userId)==="undefined"){
      return (
        <div>
        <EnterName />
      </div>
      );
    } else {
        if(userIdExistsButWrongRoom){
          return (
            <div>
            <EnterName />
          </div>
          );
        } else {
          return (
            <div>
            <Game />
            </div>);
        }
      }
  } else {
    history.push("/");
    return (<div />);
  }
}

function EnterName() {
  const {roomId} = useParams();
  const [name, setName] = useState("");
  
  function handleChange(event) {
    setName(event.target.value);
  }
  function handleSubmit(event) {
    addNewUser(roomId,name).then(function(userId) {cookies.set('userId',userId)});
  }
  return (<form onSubmit={handleSubmit}>
    <label>
      Name:
      <textarea value={name} onChange={handleChange} />
    </label>
    <input type="submit" value="Submit" />
  </form>);
}


function Game() {
  const {roomId} = useParams();
  return (<div>
    <h1>
    {roomId}
    </h1>
  </div>);
}


//Add a new user with server generated unique ID, and the given room ID to the database
//Returns the doc
function addNewUser(roomId,name) {
  const db = firebase.firestore();
  const user = db.collection("users").doc()
  const userId = user.id;
  alert(userId);
  return user.set({
    roomId: roomId,
    name: name
  }).then(() => alert("finished writing"));
  
}

Edit

After Oliver's answer, I tried implementing an asynchoronus submitHandler. However, this caused the addNewUser function to not even be called. I found this question javascript async await Submitting a form with onsubmit using Promise which suggests that the problem with an asynchronus submitHandler is that it always returns a promise so it submits before the Promise resolves. I tried implementing their proposed fix (below) but now it is back to the orginal problem but just all the time now!

function EnterName() {
  const {roomId} = useParams();
  const [name, setName] = useState("");
  
  function handleChange(event) {
    setName(event.target.value);
  }
  async function handleSubmit(event) {
    let userId = await addNewUser(roomId, name);
    alert("test");
    cookies.set('userId',userId);
  }
  function submit(event){
    handleSubmit(event);
    return false;
  }
  return (<form onSubmit={submit}>
    <label>
      Name:
      <textarea value={name} onChange={handleChange} />
    </label>
    <input type="submit" value="Submit" />
  </form>);
}


function Game() {
  const {roomId} = useParams();
  return (<div>
    <h1>
    {roomId}
    </h1>
  </div>);
}


//Add a new user with server generated unique ID, and the given room ID to the database
//Returns the doc
async function addNewUser(roomId,name) {
  const db = firebase.firestore();
  const user = db.collection("users").doc()
  const userId = user.id;
  alert(userId);
  return user.set({
    roomId: roomId,
    name: name
  }).then(() => {
    alert("finished writing");
    return userId;
  });

}

2 Answers2

1

Promise are asynchronous, so your processing may end before it resolves. Use await/async to make it wait.

Try with this:

function EnterName() {
  const {roomId} = useParams();
  const [name, setName] = useState("");
  
  function handleChange(event) {
    setName(event.target.value);
  }
  async function handleSubmit(event) {
    let userId = await addNewUser(roomId, name);
    cookies.set('userId',userId);
  }
  return (<form onSubmit={handleSubmit}>
    <label>
      Name:
      <textarea value={name} onChange={handleChange} />
    </label>
    <input type="submit" value="Submit" />
  </form>);
}


function Game() {
  const {roomId} = useParams();
  return (<div>
    <h1>
    {roomId}
    </h1>
  </div>);
}


//Add a new user with server generated unique ID, and the given room ID to the database
//Returns the doc
async function addNewUser(roomId,name) {
  const db = firebase.firestore();
  const user = db.collection("users").doc()
  const userId = user.id;
  alert(userId);
  return user.set({
    roomId: roomId,
    name: name
  }).then(() => {
    alert("finished writing");
    return userId;
  });
}
l1b3rty
  • 3,333
  • 14
  • 32
1

I finally fixed it. The problem was coming from not overriding the default behaviour of onSubmit in React. So the page was reloading before the promise was finished, and thus cutting off the thread. When I added event.preventDefault(); this was fixed.

Here is the fixed code which now works:

function EnterName(props) {
  const {roomId} = useParams();
  const [name, setName] = useState("");
  
  function handleChange(event) {
    setName(event.target.value);
  }
  function handleSubmit(event) {
    addNewUser(roomId,name).then(function(user) {
      cookies.set('userId',user.id);
      alert("finished cookies");
      props.userExists(false);
    });
    event.preventDefault();
    return false;
  }
  return (<form onSubmit={handleSubmit}>
    <label>
      Name:
      <textarea value={name} onChange={handleChange} />
    </label>
    <input type="submit" value="Submit" />
  </form>);
}


function Game() {
  const {roomId} = useParams();
  return (<div>
    <h1>
    {roomId}
    </h1>
  </div>);
}


//Add a new user with server generated unique ID, and the given room ID to the database
//Returns the doc
function addNewUser(roomId,name) {
  const db = firebase.firestore();
  return db.collection("users").add({
    roomId: roomId,
    name: name
  });
  
}