Проблема с состоянием в крючках реакции при использовании firebase-firestore

# #reactjs #google-cloud-firestore #react-hooks

Вопрос:

Я не уверен, почему, но по какой-то причине у меня нет значения старого состояния при вызове обновления, кажется, что оно всегда имеет значение состояния по умолчанию.

 const defaultState:RootState = {
    loadSessions:(groupId:string,lectureId:string)=>{},
    loadSession:(sessionId:string)=>()=>{},
    userJoined:(user:IUser)=>{}
};

const TSessionContext = React.createContext<RootState>(defaultState);

export const TSessionProvider: FC = ({ children }) => {
    const [state, setState] = useState<RootState>(defaultState);

    const loadSession = (sessionId:string)=>{
        console.info("Calling load session");
        const unsubSession = db.collection(COLLECTION_REF).doc(sessionId)
            .onSnapshot((snapshot) => {
                const session = snapshot.data() as ISession;
                console.info("setting session",state);
                setState({
                    ...state,
                    session
                });
            });

        const unsubUsers = db.collection(`${COLLECTION_REF}/${sessionId}/users`)
            .onSnapshot((querySnapshot) => {
                const users:IUser[] = [];
                querySnapshot.forEach((doc) => {
                    users.push(doc.data() as IUser)
                });
                console.info("setting users",state);
                setState({
                    ...state,
                    users
                });
            });

        return ()=>{
            unsubUsers();
            unsubSession();
        };
    }

    return (
        <TSessionContext.Provider
            value={{
                ...state,
                loadSession,
            }}
        >
            {children}
        </TSessionContext.Provider>
    );
};

export const useTSession = () =>  useContext(TSessionContext);
 

Здесь я всегда получаю вывод «настройка сеанса» со значением состояния по умолчанию и «настройка пользователей» со значением состояния по умолчанию.

Код, в котором я использую этот крючок:

 const {loadSession,session,users} = useTSession();
    
useEffect(()=>{
  if(sessionIdamp;amp;!session){
    const unsub = loadSession(sessionId);
    return () => {
      unsub()
    }
  }
},[]);
 

Затем в моем основном компоненте я session сначала получаю с user неопределенным.
А затем user с session неопределенным.

Если я добавлю еще один крючок, который извлекает больше данных, то возникнет та же проблема. Так что я действительно не уверен, в чем здесь проблема.

Если я разделю свои крючки, внутреннее состояние будет таким:

 const [iSession, setISession] = useState<any>();
const [iUser, setIUser] = useState<any>();

return (
    <TSessionContext.Provider
        value={{
            ...state,
            users:iUser,
            session:iSession,
            loadSession,
        }}
    >
        {children}
    </TSessionContext.Provider>
);
 

Тогда это, кажется, работает правильно, не знаю, почему другой вариант не работает.

Ответ №1:

setState является асинхронным, поэтому, когда вы вызываете их таким образом, вы перезаписываете предыдущее состояние. Я не видел шаблона, подобного тому, который вы используете, но вам не следует дважды пытаться записать состояние в одной и той же функции, так как вы столкнетесь с проблемами, которые, возможно, вы могли бы обновить до этого

 const loadSession = async(sessionId:string)=>{
        const newState = {}
        console.info("Calling load session");
        const unsubSession = await db.collection(COLLECTION_REF).doc(sessionId)
            .onSnapshot((snapshot) => {
                const session = snapshot.data() as ISession;
                console.info("setting session",state);
                newState.session = session
            });

        const unsubUsers = await db.collection(`${COLLECTION_REF}/${sessionId}/users`)
            .onSnapshot((querySnapshot) => {
                const users:IUser[] = [];
                querySnapshot.forEach((doc) => {
                    users.push(doc.data() as IUser)
                });
                console.info("setting users",state);
                newState.users = users
            });
            setState({
              ...state,
              ...newState,
           });

        return ()=>{
            unsubUsers();
            unsubSession();
        };
    }
 

Комментарии:

1. Хм, но onSnapshot тоже асинхронен. Таким образом, в этом случае может быть вероятность того, что коллекция пользователей будет получена до сеанса. Или я ошибаюсь?

2. да, вы правы, сделайте функцию асинхронной и добавьте ожидание, извините, что пропустил это

3. другой более простой ответ-разделить состояние на const [сеанс, сеанс] = useState(null) const [пользователи, пользователи] = useState(null) Таким образом, вам не придется беспокоиться

4. Да, я сделал это, и это сработало. Я просто хотел понять, почему версия с одним состоянием не работает.

5. это потому, что он асинхронен, поэтому состояние …смотрит на текущее состояние до запуска функции, поэтому второе завершение будет заменено первым завершенным. Я надеюсь, что мой первоначальный ответ правильно объяснил это