如何让我的函数等到用户数据在我的 useContext 提供程序中更新?

How can I make my function wait until the user data is updated in my useContext provider?

新人在这里做出反应,我希望新用户在首次登录时输入他们的详细信息。这包括输入用户名、姓名、个人资料照片等。

当他们提交了他们的详细信息后,我等待来自 firebase 的确认,然后我想将他们转发到他们的个人资料(link 结构是 domain/p/:username)。

然而,每次我尝试它时,它最终都会尝试前往 domain/p/undefined?

当我使用 React 开发工具进行检查时,我可以看到用户名已成功发送到我的状态提供者,所以我认为这只是时间问题。

欢迎页面功能如下:

//The first method begins the update and checks if the username already exists.
  const update = async (e) => {
    if (
      firstName.trim() === "" ||
      lastName.trim() === "" ||
      username.trim() === "" ||
      bio.trim() === "" ||
      addressOne.trim() === "" ||
      city.trim() === "" ||
      county.trim() === "" ||
      postCode.trim() === "" ||
      photos.length === 0
    ) {
      window.alert("Invalid data!\nOnly Address line 2 can be empty");
    } else {
      var usernameRef = db
        .collection("users")
        .where("username", "==", username);

      usernameRef.get().then((docs) => {
        if (docs.size === 1) {
          docs.forEach((doc) => {
            if (doc.id === currentUser.uid) {
              sendUpdate();
            } else {
              window.alert("Username taken");
            }
          });
        } else {
          sendUpdate();
        }
      });
    }
  };
  //This method puts the initial data into firebase except the profile picture
  function sendUpdate() {
    setLoading("loading");
    db.collection("users")
      .doc(currentUser.uid)
      .set(
        {
          username: username,
          name: firstName,
          surname: lastName,
          bio: bio,
          address1: addressOne,
          address2: addressTwo,
          notifications: [],
          city: city,
          county: county,
          postcode: postCode,
          newUser: false,
        },
        { merge: true }
      )
      .then(() => {
        updatePhoto();
      })
      .catch((err) => console.log(err));
  }
  //This method uploads the profile picture, then gets the downloadURL of the photo just uploaded and puts it into the user document created in method 2.
  //It also trys to send the user to their profile afterwards, but it always ends up as undefined.
  const updatePhoto = async () => {
    const promises = [];
    var userREF = db.collection("users").doc(currentUser.uid);
    photos.forEach((photo) => {
      const uploadTask = firebase
        .storage()
        .ref()
        .child(
          `users/` + currentUser.uid + `/profilePicture/profilePicture.jpg`
        )
        .put(photo);
      promises.push(uploadTask);
      uploadTask.on(
        firebase.storage.TaskEvent.STATE_CHANGED,
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          if (snapshot.state === firebase.storage.TaskState.RUNNING) {
            console.log(`Progress: ${progress}%`);
          }
        },
        (error) => console.log(error.code),
        async () => {
          const downloadURL = await uploadTask.snapshot.ref.getDownloadURL();
          userREF
            .update({
              profilePicture: downloadURL,
            })
            .then(async () => {
              updateUserData().then(() => {
                setLoading("complete");
                setTimeout(() => {
                  history.push("/p/" + userData.username);
                }, 3000);
              });
            });
        }
      );
      return "completed";
    });
  };

这是我的 AuthContext 提供程序:(函数 UpdateUserData() 是在将数据放入 firebase 后更新数据)

import React, { useContext, useState, useEffect } from "react";
import { auth, db } from "../firebase";

const AuthContext = React.createContext();

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState();
  const [userData, setUserData] = useState();
  const [loading, setLoading] = useState(true);

  function signup(email, password) {
    return auth.createUserWithEmailAndPassword(email, password);
  }

  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password);
  }

  async function updateUserData() {
    if (currentUser) {
      var userData = db.collection("users").doc(currentUser.uid);
      await userData
        .get()
        .then((doc) => {
          if (doc.exists) {
            setUserData(doc.data());
            return "success";
          }
        })
        .catch((error) => {
          console.log("Error getting document:", error);
          return "error";
        });
    }
  }

  function logout() {
    setUserData();
    return auth.signOut();
  }

  function resetPassword(email) {
    return auth.sendPasswordResetEmail(email);
  }

  function updateEmail(email) {
    return currentUser.updateEmail(email);
  }

  function updatePassword(password) {
    return currentUser.updatePassword(password);
  }

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setCurrentUser(user);
      setLoading(false);
      if (user) {
        var userData = db.collection("users").doc(auth.currentUser.uid);
        userData
          .get()
          .then((doc) => {
            if (doc.exists) {
              setUserData(doc.data());
            }
          })
          .catch((error) => {
            console.log("Error getting document:", error);
          });
      }
    });

    return unsubscribe;
  }, []);

  const value = {
    currentUser,
    userData,
    updateUserData,
    login,
    signup,
    logout,
    resetPassword,
    updateEmail,
    updatePassword,
  };

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

如您所见,尝试加载未定义的页面后,我们可以看到用户名实际上最终出现在我的上下文提供程序的 userData 中:

TIA!

您可以通过将重定向 link 移出您 updatePhoto 并将其放入 useEffect(或基于代码流的任何其他选项)来解决此问题,然后只需设置一个状态或检查所需的数据如 userdata.userName 已经存在,如果它的未定义阻止重定向并且你可以显示加载程序组件,否则执行重定向...

基本示例:

useEffect(() => {
  if(userData.username){
    history.push("/p/" + userData.username);
  }
}, [userData.username])


const myUpdateFunction = useCallBack(() => {
  fetch().then(v => {
   setUserData(v);
  })
}, [])