Problem with state in react hooks while using firebase-firestore

Viewed 84

I'm not sure why but for some reason I don't have the value of old state when calling update, it always seems to have the value of default state.

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);

Here I always get output "setting session" with default state value and "setting users" with default state value.

Code where I am using this hook:

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

Then in my main component I get session first with user undefined. And then user with session undefined.

If I add another hook that fetches more data, then it has the same issue. So I'm really not sure what is the problem here.

If I split my hooks internal state like this:

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

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

Then it seems to work correctly, not sure why the other variant doesn't.

1 Answers

setState is asynchronous so when you call them like that you are overwriting the previous state. I haven't seen a pattern like the one you are using but you shouldn't try write state twice in the same function as you will face problems you could maybe update to this

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();
        };
    }
Related