reactjsreact-nativeexpo

How to refetch data in a component after navigating back to it using navigation.navigate()?


I have a React Native app where I'm navigating to the StudioServices screen after adding a new service. However, when navigating back to the StudioServices screen, the data is not refetched. The issue arises because the useStudioServices hook fetches data only when the component initially mounts, and not when the screen gains focus after navigating back.

Here’s the setup:

StudioServices Screen - Displays a list of services. useStudioServices Hook - Fetches data from the API (GetStudioServices) and returns the services, loading, and error states. ServiceAdd Screen - Navigates to StudioServices after saving a new service.

const StudioServices = () => {
const { services, loading, error } = useStudioServices();

if (loading) {
  return <LoadingScreen />;
}

if (error) {
  return (
    <View>
      <Text>Error</Text>
    </View>
  );
}

return <ServiceList services={services} />;
};

useStudioServices Hook:

export const useStudioServices = () => {
  const [services, setServices] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      const result = await GetStudioServices();
      setServices(result);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };
  
  useEffect(() => {
    fetchData();
  }, []);

  return { services, loading, error };
};

ServiceAdd Screen:

const ServiceAdd = () => {
  const navigation = useNavigation();

  const handleSave = async (newService) => {
    await InsertService(newService);
    navigation.navigate("StudioServices", { newService });
  };
};

Solution

  • You have to make some changes in your hook file see below code:

    import { useState, useCallback } from 'react';
    
    export const useStudioServices = () => {
      const [services, setServices] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      const fetchData = useCallback(async () => {
        try {
          setLoading(true);
          const result = await GetStudioServices();
          setServices(result);
        } catch (error) {
          setError(error);
        } finally {
          setLoading(false);
        }
      }, []); // useCallback ensures the function reference remains stable
    
      useEffect(() => {
        fetchData();
      }, [fetchData]);
    
      return { services, loading, error, fetchData };
    };
    

    Now you can call fetchData in screen when you come back to screen, there are two was for that, one is with navigation listner and other is with useFocusEffect and useCallback hooks.

    With hooks:

    import { useFocusEffect } from '@react-navigation/native';
    
    const StudioServices = () => {
      const { services, loading, error, fetchData } = useStudioServices();
    
      // Refetch data whenever the screen comes into focus
      useFocusEffect(
        React.useCallback(() => {
          fetchData();
        }, []) // Add dependencies if needed
      );
    
      if (loading) {
        return <LoadingScreen />;
      }
    
      if (error) {
        return (
          <View>
            <Text>Error</Text>
          </View>
        );
      }
    
      return <ServiceList services={services} />;
    };
    

    With navigation listner:

    import { useEffect } from 'react';
    import { useNavigation } from '@react-navigation/native';
    
    const StudioServices = () => {
      const navigation = useNavigation();
      const { services, loading, error, fetchData } = useStudioServices();
    
      useEffect(() => {
        const unsubscribe = navigation.addListener('focus', () => {
          fetchData();
        });
    
        return unsubscribe; // Cleanup the listener when the component unmounts
      }, [navigation, fetchData]);
    
      if (loading) {
        return <LoadingScreen />;
      }
    
      if (error) {
        return (
          <View>
            <Text>Error</Text>
          </View>
        );
      }
    
      return <ServiceList services={services} />;
    };