I have made an app using firebase
email/pass authentication and managing the state using redux-toolkit
, everything is working fine, except, a bad UX, that is, when the app is opened, it takes a second to determine whether the user is logged-in or not, so for that, I have a loading state in redux, and that is also working fine, but during the initial load, I could see my LoginScreen display first and then after a second or two, if the user was logged-in previously, it display my HomeScreen, so I need to provide a LoadingScreen in order to hide that transition, and only display a particular screen when every loading operation is done.
I tried adding a local state and setting it to false when everything is done inside the onAuthStateChange
function but, it does not work.
Video of my problem: https://drive.google.com/file/d/1a8Bu1bTqAANsWKDs0e5bjqVdZJWJRcMv/view?usp=sharing
Please take a look at the recording (link above) so that you understand the situation correctly!
Below is my code, the entry file and the redux toolkit state file:
Entry file:
const EntryPoint = () => {
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user);
const authloading = useSelector(state => state.auth.loading);
useEffect(() => {
onAuthStateChanged(auth, result => {
if (result) {
const path = doc(db, 'users', result.uid);
getDoc(path)
.then(result => {
if (result.exists()) {
const data = result.data();
dispatch(
login({
displayName: data['displayName'],
email: data['email'],
photoURL: data['photoURL'],
phone: data['phone'],
}),
);
}
})
.catch(error => console.log('sign in error: ', error));
} else {
dispatch(logout());
}
dispatch(loading(false));
});
}, []);
if (authloading) return <Loader />;
return (
<Stack.Navigator>
{user ? <Stack.Screen name="Home" component={Home} /> : <Stack.Screen name="SignIn" component={SignIn} />}
</Stack.Navigator>
);
};
Redux state:
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
user: null,
loading: true,
}
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
loading: (state, action) => {
state.loading = action.payload;
},
}
});
CodePudding user response:
I think this is happening because you set loading = false
before you set user
and this is quite clear because user
will be setted after getDoc
call (that creates that 1 seconds or 2 of "bad UX").
Redux dispatch
returns a Promise, so you could use that promise to set loading
to false after user is setted:
const EntryPoint = () => {
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user);
const authloading = useSelector(state => state.auth.loading);
useEffect(() => {
onAuthStateChanged(auth, result => {
if (result) {
const path = doc(db, 'users', result.uid);
getDoc(path)
.then(result => {
if (result.exists()) {
const data = result.data();
dispatch(
login({
displayName: data['displayName'],
email: data['email'],
photoURL: data['photoURL'],
phone: data['phone'],
}),
)
.then(result => { dispatch(loading(false)); }); //<-- after user is setted, remove loading
}
else dispatch(loading(false)); //<-- if result does not exixsts, remove loading
})
.catch(error => {
dispatch(loading(false)); //<-- in case of error, remove loading
console.log('sign in error: ', error);
});
} else {
dispatch(logout())
.then(result => { dispatch(loading(false)); }); //<-- after logout, remove loading
}
});
}, []);
if (authloading) return <Loader />;
return (
<Stack.Navigator>
{user ? <Stack.Screen name="Home" component={Home} /> : <Stack.Screen name="SignIn" component={SignIn} />}
</Stack.Navigator>
);
};
In this way you are 100% sure that loading will be setted to false only after user is setted (avoiding bad UX).