1

I want to redirect to the next screen when signIn succefull, however I'm having issues with the redux implementation. I'm able to complete the signIn/signUp and get the current user but in the file that is supposed to contain the routes, I'm not getting the user info. When I succesfull sigIn, the screen remains the same. My repo: https://github.com/carlos-ediaz/tap.git

App.jsx code:

import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
import React from "react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import rootReducer from "./src/redux/reducers";
import { thunk } from "redux-thunk";

import Route from "./src/navigation/main";

const store = createStore(rootReducer, applyMiddleware(thunk));

export default function App() {
  return (
    <Provider store={store}>
      <Route />
    </Provider>
  );
}

My index.js file (Exports :

import { View, Text } from "react-native";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getCurrentUserInfo} from "../../redux/actions";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import AuthScreen from "../../screens/auth";
import HomeScreen from "../home";
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { fdb } from "../../../db";

const Stack = createNativeStackNavigator();
const auth = getAuth(fdb);
export default function Route() {
  const currentUserObj = useSelector((state) => state.auth);
  const dispatch = useDispatch();

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      console.log("Executing dispatch");
      if (user) {
        dispatch(getCurrentUserInfo());
      } else {
        dispatch({
          type: USER_STATE_CHANGE,
          currentUser: null,
        });
      }
    });
    return unsubscribe;
  }, []);

  if (!currentUserObj?.loaded) {
    return (
      <View>
        <Text>index</Text>
      </View>
    );
  }

  return (
    <NavigationContainer>
      <Stack.Navigator>
        {currentUserObj.currentUser ? (
          <Stack.Screen name="home" component={HomeScreen} options={{ headerShown: false }}/>
        ) : (
          <Stack.Screen name="auth" component={AuthScreen} options={{ headerShown: false }} />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

My redux/actions file:

import { collection, doc, getDoc, onSnapshot, getFirestore, setDoc } from "firebase/firestore";
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged, } from "firebase/auth";
import { fdb } from "../../../db";
import { USER_STATE_CHANGE } from "../constants";

const auth = getAuth(fdb);

export const userAuthStateListener = () => (dispatch) => {
  onAuthStateChanged(auth, (user) => {
    if (user) {
      dispatch(getCurrentUserInfo());
    } else {
      dispatch({ type: USER_STATE_CHANGE, currentUser: null, loaded: true });
    }
  });
};

export const getCurrentUserInfo = () => async (dispatch) => {
  const db = getFirestore(fdb);
  const userRef = collection(db, "user");
  const docRef = doc(userRef, auth.currentUser.uid);
  const res = await getDoc(docRef);
  console.log(res.data());
  return dispatch({
    type: USER_STATE_CHANGE,
    currentUser: res.exists ? res.data() : null,
    loaded: true,
  });
};

export const login = (email, password) => (dispatch) =>
  new Promise((resolve, reject) => {
    signInWithEmailAndPassword(auth, email, password)
      .then(() => {
        resolve();
      })
      .catch((error) => {
        reject(error);
      });
  });

export const register = (email, password) => (dispatch) =>
  new Promise((resolve, reject) => {
    createUserWithEmailAndPassword(auth, email, password)
      .then(() => {
        resolve();
      })
      .catch((error) => {
        reject(error);
      });
  });

My redux/reducers/auth file:

import { USER_STATE_CHANGE } from "../constants";

const initialState = {
  currenUser: null,
  loaded: false,
};

export const auth = (state = initialState, action) => {
  switch (action.type) {
    case USER_STATE_CHANGE:
      return {
        ...state,
        currenUser: action.currenUser,
        loaded: action.loaded,
      };
    default:
      return state;
  }
};

My redux/reducers/index file:

import { combineReducers } from "redux";
import { auth } from "./auth";
const Reducers = combineReducers({
  auth,
});

export default Reducers;

constants file:

export const USER_STATE_CHANGE = 'USER_STATE_CHANGE'

I have put a lot of console.log but there's something with the function becasue the useEffect only calls the first time and if i put ...},[currentUserObj]); calls the function all the time but doesnt update the user info.

I'm done. Not sure what to do. really stuck

1 Answer 1

0

You appear to not quite use the Firebase handlers and Redux correctly.

onAuthStateChanged only needs to be called once with the appropriate callbacks passed to it to handle authentication changes. Instantiate a call to onAuthStateChanged in a useEffect hook and return a cleanup function to unsubscribe the listener.

Example:

const auth = getAuth(fdb);

export default function Route() {
  const dispatch = useDispatch();

  const currentUserObj = useSelector((state) => state.auth);

  // Mounting effect to instantiate the auth listener
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        dispatch(getCurrentUserInfo());
      } else {
        dispatch({
          type: USER_STATE_CHANGE,
          currentUser: null,
        });
      }
    });

    // Unsubscribe when component unmounts
    return unsubscribe;
  }, []);

  if (!currentUserObj?.loaded) {
    return (
      <View>
        <Text>No loaded</Text>
      </View>
    );
  }

  return (
    <NavigationContainer>
      <Stack.Navigator>
        {currentUserObj.currentUser ? (
          <Stack.Screen name="home" component={HomeScreen} />
        ) : (
          <Stack.Screen name="auth" component={AuthScreen}/>
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Similarly, the getCurrentUserInfo action only needs to trigger getting the current user's information once. onSnapshot instantiates a listener, but you likely don't want to do this, especially since you don't clean them up, e.g. there is no logic to unsubscribe from changes. You could use getDoc to fetch the data once.

Example update:

export const getCurrentUserInfo = () => async (dispatch) => {
  const db = getFirestore(fdb);
  const docRef = doc(db, "users", auth.currentUser.uid);
  const doc = await getDoc(docRef);

  return dispatch({
    type: USER_STATE_CHANGE,
    currentUser: doc.exists ? doc.data() : null,
  });
};

Handle the "isLoading" and "loaded" states in the reducer(s) when processing the dispatched actions.

4
  • Thank you so much, helped a lot! but currentUserObj is still empty, doesn't save the currentUser info Maybe there's something wrong with the useSelector, I'm not sure Commented Dec 13, 2023 at 19:35
  • @CarlosEduardoDiazTorres Yeah, I basically assumed your Redux state was being maintained correctly. Can you edit your post to include the reducers and store code so we can see how the state is being maintained?
    – Drew Reese
    Commented Dec 13, 2023 at 19:39
  • Thank you, I updated the code and included what you kindly suggested Commented Dec 13, 2023 at 20:24
  • 1
    Fixed typo errors, and wasn't calling the firebase (doc, collection) properly. Your code really helped me, thank you so much Commented Dec 13, 2023 at 23:05

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.