How to wait for a successful async action before changing url?

Viewed 583

so I'm using a popup to log my users in with firebase:

const loginToApp = (provider) => {
    firebaseApp
        .auth()
        .signInWithPopup(provider)
        .then(async (result) => {
            if (result.additionalUserInfo.isNewUser) {
                // problem is this line
                await setNewUserInformation(result.user.uid)
            }
            const { user } = result
            setUser(user)
            // and this line
            window.location.href = 'newRoute'
        })
        .catch((error) => {
            console.log('ERROR:', error)
        })
}

so if I remove window.location.href = 'visited' this all works fine and it sets in firebase. I'm probably doing something stupid but I cant figure out how to wait for this function to fire setNewUserInformation and to complete before I move to the new page?

function code:

export const setNewUserInformation = (userId) => {
    return {
        type: 'SET_NEW_USER_INFORMATION',
        userId,
    }
}

this then has a redux observable epic listening to it:

return action$.pipe(
    ofType('SET_NEW_USER_INFORMATION'),
    mergeMap((action) => {
        return from(
            firebaseApp.database().ref(firebaseRef).update(userInformation),
        ).pipe(
            mergeMap(() => {
                return [updatedUserInformationSuccess()]
            }),
            catchError((error) => of(updatedUserInformationFailure(error))),
        )
    }),
)
4 Answers

setNewUserInformation() is an action creator, which is sync. You do not need to wait for it as it does not return anything useful to you logic. What you need to do, is move window.location.href = 'newRoute' to separate logic, and make it depend on state returned from action creators updatedUserInformationSuccess() and updatedUserInformationFailure(error). If your component is functional, put this logic in a useEffect. If it is a class component, use ComponentDidUpdate lifecycle method.

Use it like below

const loginToApp = (provider) => {
firebaseApp
    .auth()
    .signInWithPopup(provider)
    .then(async (result) => {
        new Promise((resolve, reject) => {
             if (result.additionalUserInfo.isNewUser) {
                // problem is this line
                setNewUserInformation(result.user.uid)
            }
           const { user } = result
           resolve(user)
        }).then((user)=>{
          setUser(user)
          // and this line
          window.location.href = 'newRoute'
        })
       
    })
    .catch((error) => {
        console.log('ERROR:', error)
    })
  }

Because on then You can returned a Promise and resolve later. We could re-write the code above like this below:

const loginToApp = (provider) => {
    firebaseApp
        .auth()
        .signInWithPopup(provider)
        .then((result) => {
            if (result.additionalUserInfo.isNewUser) {
                // return for next resolve function
                return setNewUserInformation(result.user.uid).then(() => result); 

            }
            return result;
        })
        .then((result) => {
            // after all above promises resolve
            const { user } = result
            setUser(user)
            // and this line
            window.location.href = 'newRoute'
        })
        .catch((error) => {
            console.log('ERROR:', error)
        })
}

Are you using React? If yes, then you can simply use didUpdate Cycle to route to new url after successful action dispatched. Move your "window.location.href = 'newRoute'" under the ComponentDidUpdate with props check.

Related