2

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 Answer 1

1

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();
        };
    }
Sign up to request clarification or add additional context in comments.

5 Comments

Hmm, but onSnapshot is async as well. So in this case there might be a chance that users collection is fetched before session. Or am I wrong?
yes you are right, make func async and add await, sorry missed that
another simpler answer is to split state have const [session, setSession] = useState(null) const [users, setUsers] = useState(null) That way you dont have to worry
Yes I did that and it worked. I just wanted to understand why the version with one state doesn't work.
its because its async so the the ...state looks at the current state before the the function starts so the second to complete will overright the first completed. I hope my original answer explained that properly

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.