In some case I'd like to delay some events until I'm sure the auth state is ready and that the redux store contains the auth state of the signed-in user.
Typically I'd like to delay generating the UI until I'm sure that the auth state is ready which happens after the auth().onAuthStateChanged listener has fired once. There are multiple reasons why:
On the client I'd want to avoid a potential "refresh" of the UI where we'd display the UI of a signed-out user first and then redisplay once the redux store has loaded the signed-in users..
But this is really mandatory on the server as I really have to wait for the auth state to be ready because I only get one chance of generating the UI :)
In my app, as a workaround, I have a small tool that does this:
// Auth state promise resolver.
let authReadyPromiseResolver;
const authReadyPromise = new Promise(resolve => {
authReadyPromiseResolver = resolve
});
const unsubscribe = firebaseApp.auth().onAuthStateChanged(() => {
authReadyPromiseResolver();
unsubscribe();
});
Then I can wait for the authReadyPromise promise to resolve and I know the firebase auth state si now ready. e.g. on a server I'd do:
// ...
authReadyPromise.then(() => { // HERE I wait for the auth state to be ready.
// Render the App.
const body = ReactDOMServer.renderToString(
React.createElement(app.App, {store: store, history: history})
);
// Get the redux store state.
const initialState = store.getState();
// Serve the app generated template
res.send(template({body, initialState}));
);
But really this is a workaround and I'm lucky that this works because my onAuthStateChanged event listener could very well be triggered before the one you use internally inside of react-redux-firebase.
So Feature request is:
It would be great to have a Promise I could use that would be built-in react-redux-firebase that would resolve when the auth state is ready and all the Redux store state has been updated. For instance, I'm thinking of:
const reactReduxFirebaseMiddleware = reactReduxFirebase(firebaseApp, {enableRedirectHandling: false});
const authIsReadypromise = reactReduxFirebaseMiddleware.authIsReady; // THIS IS THE BUILT-IN PROMISE
const store = createStore(
combineReducers({
...reducers,
router: routerReducer,
firebaseState: firebaseStateReducer
}),
initialState,
compose(
applyMiddleware(thunk.withExtraArgument(getFirebase)),
applyMiddleware(historyMiddleware),
reactReduxFirebaseMiddleware
)
);
authIsReadypromise.then(() => { // HERE I wait for the auth state to be ready.
// Render the App.
const body = ReactDOMServer.renderToString(
React.createElement(app.App, {store: store, history: history})
);
// ...
Not sure if it should be on reactReduxFirebaseMiddleware or if there is somewhere more appropriate.
If there is another built in way to easily know when the auth state is ready please lmk, I'm kinda new-ish to redux so I may be missing some stuff :)
Love the idea for the feature, and it is great to see how you think it would be used. I am going to start experimenting.
Here are a few existing things that may help with the meantime solution:
onAuthStateChange config option (hooks into internal onAuthStateChange)AUTHENTICATION_INIT_FINISHED action type is emitted after auth is setup (maybe subscribe and wait for that?)isLoaded and isEmpty helpers will be helpful if you are trying to check auth state existenceThanks @prescottprue !
FYI this is what I've used in the mean time:
/**
* Returns a promise that completes when Firebase Auth is ready in the given store using react-redux-firebase.
*
* @param {Object} store - The Redux store on which we want to detect if Firebase auth is ready.
* @param {string} [firebaseReducerAttributeName] - The attribute name of the react-redux-firebase reducer when using multiple combined reducers.
* 'firebaseState' by default. Set this to `null` to indicate that the react-redux-firebase reducer is not in a combined reducer.
* @return {Promise} - A promise that completes when Firebase auth is ready in the store.
*/
export function whenAuthReady(store, firebaseReducerAttributeName = 'firebaseState') {
const isAuthReady = store => {
const state = store.getState();
const firebaseState = firebaseReducerAttributeName ? state[firebaseReducerAttributeName] : state;
const firebaseAuthState = firebaseState && firebaseState.auth;
if (!firebaseAuthState) {
throw new Error(`The Firebase auth state could not be found in the store under the attribute '${firebaseReducerAttributeName ? firebaseReducerAttributeName + '.' : ''}auth'. Make sure your react-redux-firebase reducer is correctly set in the store`);
}
return firebaseState.auth.isLoaded;
};
return new Promise(accept => {
if (isAuthReady(store)) {
console.log('Redux store Firebase auth state is ready!');
accept();
} else {
const unsubscribe = store.subscribe(() => {
if (isAuthReady(store)) {
console.log('Redux store Firebase auth state is ready!');
unsubscribe();
accept();
}
});
}
});
}
Got something added to v2.0.0-beta.9 (not published yet though). It actually uses basically exactly what you provided @nicolasgarnier.
Since reactReduxFirebase is a store enhancer it can/does attach things to the store (for not just store.firebase). For this case I attached a promise called firebaseAuthIsReady to the store.
const store = createStore(
combineReducers({
...reducers,
router: routerReducer,
firebaseState: firebaseStateReducer
}),
initialState,
compose(
reactReduxFirebase(firebaseApp, {
firebaseStateName: 'firebaseState', // is 'firebase' by default, so you must set to 'firebaseState'
enableRedirectHandling: false
}),
applyMiddleware(thunk.withExtraArgument(getFirebase)), // Only needed if using getFirebase within thunks
applyMiddleware(historyMiddleware),
)
);
store.firebaseAuthIsReady.then(() => { // state is ready here
// Render the App.
})
attachAuthIsLoaded config option was added to allow for disabling the attaching of thefirebaseAuthIsLoaded promise to the redux store.
reactReduxFirebase(firebaseApp, {
attachAuthIsReady: false // store.firebaseAuthIsReady will be undefined
})
For those who want to change how the internal creation of the authIsReady promise can provide a function to the authIsReady config parameter.
reactReduxFirebase(firebaseApp, {
attachAuthIsReady: true, // true by default
authIsReady: (store, config) => {
// write your own logic here, but make sure to return a promise!
}
})
The internal authIsReady function is also exposed so it can be used by itself. It accepts store and firebaseStateName
import { authIsReady } from 'react-redux-firebase'
// authIsReady(store, firebaseStateName)
authIsReady(store, 'firebase')
.then(() => {
console.log('auth is ready')
})
Included in the v2.0.0-beta.9 release. Let me know if it doesn't work as expected or cover what you needed. Thanks for the feature suggestion!
Hey Scott,
I'm trying to use this feature now but I am not able to.
Here is my code to create the store:
export function makeStore(history, firebaseApp, initialState = {}) {
const historyMiddleware = routerMiddleware(history);
const firebaseEnhancer = reactReduxFirebase(firebaseApp, {enableRedirectHandling: false});
const store = createStore(
combineReducers({
...reducers,
router: routerReducer,
firebaseState: firebaseStateReducer
}),
initialState,
compose(
applyMiddleware(thunk.withExtraArgument(getFirebase)),
applyMiddleware(historyMiddleware),
firebaseEnhancer
)
);
store.firebaseAuthIsReady().then(() => console.log('AUTH READY')); // This FAILS
firebaseEnhancer.authIsReady().then(() => console.log('AUTH READY')); // This FAILS TOO
return store;
}
I'm always getting errors such as There was an error TypeError: store.firebaseAuthIsReady is not a function.
And yes I made sure I upgraded to beta.9 and above :)
Any idea?
I just also tried:
firebaseEnhancer.authIsReady.then(() => console.log('AUTH READY'));
store.firebaseAuthIsReady.then(() => console.log('AUTH READY'));
Since firebaseAuthIsReady is probably directly a Promise and not a function but I get this error:
info: There was an error TypeError: Cannot read property 'then' of undefined
OK I found the issue. It looks like the config variable to enable this is not set to true by default.
Also we need to use attachAuthIsReady: true and not attachAuthIsLoaded as indicated in the beta.9 release notes :) probably this was changed after beta.9
@prescottprue
I'm facing an issue where firebase.auth.isLoaded is never true initially if we are starting with no signed-on user. My expectation would be that firebase.auth.isLoaded is true whenever the initial auth state is reflected in the Redux store, no matter if there is a signed-in user or not.
Basically if I use this new feature after beta.9 to wait to load the entire UI the UI of my app is never loaded (because every user eventually start the app without any Firebase user signed-in. And basically the Promise never resolves.
It looks like this is a regression in beta.9 I don't think I was facing this issue in beta 8.
Most helpful comment
Thanks @prescottprue !
FYI this is what I've used in the mean time: