Quantcast
Channel: Active questions tagged react-native+typescript - Stack Overflow
Viewing all articles
Browse latest Browse all 6208

React Native useEffect is calling Firestore listeners more than once

$
0
0

I'm doing a mobile application using React Native (0.61.2) and TypeScript (3.6.4). I'm using Firebase Authentication and Firebase Firestore from React Native Firebase collection.

I'm trying to:

  1. Listening Auth state changes (using onAuthStateChanged)
  2. Listening user auth changes (using onUserChanged)
  3. Listening user's document (using onSnapshot)
  4. Listening a specific document called brand (using onSnapshot)

I'm saving in the Context the userAuth, user document and the brand document from the previous listeners.

/**
 * Types
 */
type UserAuth = FirebaseAuthTypes.User | null;
type User = UserDoc | null;
type Brand = BrandDoc | null;
type ContextProps = {
  userAuth: UserAuth;
  user: User;
  brand: Brand;
} | null;

/**
 * Context
 */
export const Context = createContext<ContextProps>(null);

function App() {
  const [initializing, setInitializing] = useState(true);

  const [listenUserAuth, setListenUserAuth] = useState(false);
  const [userAuth, setUserAuth] = useState<UserAuth>(null);

  const [listenUser, setListenUser] = useState(false);
  const [user, setUser] = useState<User>(null);

  const [listenBrand, setListenBrand] = useState(false);
  const [brand, setBrand] = useState<Brand>(null);

  /** Listen for auth state changes */
  useEffect(() => {
    const authListener = auth().onAuthStateChanged(result => {
      setUserAuth(result);
      if (initializing && !listenUserAuth) {
        setInitializing(false);
        setListenUserAuth(true);
        setListenUser(true);
        setListenBrand(true);
      }
    });

    return () => {
      if (authListener) {
        console.log('removing auth state listener');
        authListener();
      }
    };
  }, [initializing, listenUserAuth]);

  /** Listen for user auth changes */
  useEffect(() => {
    let userAuthListener: () => void;

    if (listenUserAuth) {
      userAuthListener = auth().onUserChanged(result => {
        setUserAuth(result);
      });
    }

    return () => {
      if (userAuthListener) {
        console.log('removing user auth listener');
        userAuthListener();
      }
    };
  }, [listenUserAuth]);

  /** Listen for user document changes */
  useEffect(() => {
    let userListener: () => void;

    if (listenUser) {
      if (!userAuth) {
        return;
      }

      console.log('listening user document');
      userListener = firestore()
        .collection('users')
        .doc(userAuth.uid)
        .onSnapshot(querySnapshot => {
          console.log('User querySnapshot: ', querySnapshot.data());
          setUser(querySnapshot.data());
        });
    }

    return () => {
      if (userListener) {
        console.log('removing user document listener');
        userListener();
      }
    };
  }, [listenUser, userAuth]);

  /** Listen for brand document changes */
  useEffect(() => {
    let brandListener: () => void;

    if (listenBrand) {
      if (!userAuth) {
        return;
      }

      console.log('listening brand document');
      brandListener = firestore()
        .collection('brands')
        .doc('31uOUtUkVYg8z953UdxS')
        .onSnapshot(querySnapshot => {
          console.log('Brand querySnapshot: ', querySnapshot.data());
          setBrand(querySnapshot.data());
        });
    }

    return () => {
      if (brandListener) {
        console.log('removing brand document listener');
        brandListener();
      }
    };
  }, [listenBrand, userAuth]);

  if (initializing) {
    return (
      <View style={{flex: 1, justifyContent: 'center'}}>
        <ActivityIndicator size="large" color="#000" />
      </View>
    );
  }

  function container(children: ReactNode | ReactNode[]) {
    return <PaperProvider theme={theme}>{children}</PaperProvider>;
  }

  return container(
    userAuth && user && brand ? (
      <Context.Provider value={{userAuth, user, brand}}>
        <SignedInStack />
      </Context.Provider>
    ) : (
      <SignedOutStack />
    )
  );
}

export default App;

In another component, when the user signs out, all the listeners are listening again and removing itself more than once.

Logs:

enter image description here

This is adding unnecessary reads and writes to Firebase which increase costs.

I'm new using useEffect and I'm pretty sure that I'm doing something wrong. I don't understand completely the dependencies needed for the useEffect to start listening the user's document and the brand's document once and to remove the listeners once. I tried to read before doing this and I thought I got it right.

  • Also, for some reason that I don't see, the ActivityIndicator is removed without the user's document and the brand's document. It should be visible until the UserAuth, User document and Brand document are set.

My goal here is:

  1. Show the ActivityIndicator until all the listeners are set.
  2. Listen and remove the listeners just once to avoid unnecessary reads and writes.

Viewing all articles
Browse latest Browse all 6208

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>