Home > other >  react native protect authenticated screens during initial loading
react native protect authenticated screens during initial loading

Time:05-21

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

  • Related