skip to Main Content

I started several days ago to create my first app, I am not deep into the useEffect logic if somebody can optimize this code in the correct logic way i will learn a lot of it.

The issue in this code is fetchData it is called when apiToken and userId still null, surely on a wrong logic in how to use useEffect.
Thanks!

export const SitesScreen = ({navigation}) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [apiToken, setApiToken] = useState(null);
  const [userId, setUserId] = useState(null);

  const fetchData = async () => {
    setIsRefreshing(true);
    await fetch(
      global.endpoint +
        '/sites/current-user-sites/' +
        userId +
        '?access-token=' +
        apiToken,
      {
        method: 'GET',
      },
    )
      .then(response => response.json())
      .then(responseJson => {
        if (responseJson.success) {
          setData(responseJson.data);
          console.log(responseJson);
        } else {
        }
        setLoading(false);
        setIsRefreshing(false);
        console.log(apiToken);
        console.log(userId);
      });
  };

  const getData = async () => {
    let deviceData = await AsyncStorage.getItem('device_data');
    deviceData = JSON.parse(deviceData);
    setApiToken(deviceData.access_token);
    setUserId(deviceData.id);
    console.log(deviceData);
  };

  useEffect(() => {
    getData().then(response => {
      fetchData();
    });
  }, [apiToken, userId]);

  const renderItem = ({
    item,
    index,
  }: {
    item: IListItem,
    index: number,
  }): React.ReactElement => (
    <ListItem
      title={`${item.name}`}
      description={`${item.address + ', ' + item.number + ' - ' + item.city}`}
      onPress={() =>
        navigation.navigate('Details', {
          siteId: item.id,
        })
      }
    />
  );

  return (
    <SafeAreaView style={GlobalStyles.safeArea}>
      <TopNavigation
        title={evaProps => (
          <Text style={GlobalStyles.headerTitle}>Cantieri</Text>
        )}
        alignment="center"
      />
      <Divider />
      <FlatList
        data={data}
        refreshControl={
          <RefreshControl refreshing={isRefreshing} onRefresh={fetchData} />
        }
        ItemSeparatorComponent={Divider}
        renderItem={renderItem}
      />
    </SafeAreaView>
  );
};

3

Answers


  1. You can use like below

    const fetchData = async () => {
        setIsRefreshing(true);
        
        let deviceData = await AsyncStorage.getItem('device_data');
        deviceData = JSON.parse(deviceData);
        let token = deviceData.access_token;
        setApiToken(deviceData.access_token);
        let uId = deviceData.id;
        setUserId(deviceData.id);
    
        await fetch(
          global.endpoint +
            '/sites/current-user-sites/' +
            uId +
            '?access-token=' +
            token,
          {
            method: 'GET',
          },
        )
          .then(response => response.json())
          .then(responseJson => {
            if (responseJson.success) {
              setData(responseJson.data);
              console.log(responseJson);
            } else {
            }
            setLoading(false);
            setIsRefreshing(false);
            console.log(apiToken);
            console.log(userId);
          });
      };
    
      useEffect(() => {
        fetchData();
      }, []);
    
    Login or Signup to reply.
  2. I believe the issue here isn’t really with the way you are using useEffect but they fact that you are expecting the state for userId and api token to have their values set before the fetchData function is called.

    React tries to reduce the number of renders that it makes by holding onto state changes for the next render because of the way you have your getData and FetchData separated its going to run both those functions and then update the state value on the next render.

    So you can combine your two methods into one so you don’t reference the state values but just reference the returned values. You could also use multiple useEffects for a super quick solution but I don’t advise this since it will cause a second render every load, or I change your state variables to Refs like so:

    export const SitesScreen = ({navigation}) => {
      const [data, setData] = useState([]);
      const [loading, setLoading] = useState(true);
      const [isRefreshing, setIsRefreshing] = useState(false);
      const apiToken = useRef(null);
      const userId = useRef(null);
    
      const fetchData = async () => {
        setIsRefreshing(true);
        await fetch(
          global.endpoint +
            '/sites/current-user-sites/' +
            userId.current +
            '?access-token=' +
            apiToken.current,
          {
            method: 'GET',
          },
        )
          .then(response => response.json())
          .then(responseJson => {
            if (responseJson.success) {
              setData(responseJson.data);
              console.log(responseJson);
            } else {
            }
            setLoading(false);
            setIsRefreshing(false);
            console.log(apiToken);
            console.log(userId);
          });
      };
    
      const getData = async () => {
        let deviceData = await AsyncStorage.getItem('device_data');
        deviceData = JSON.parse(deviceData);
        apiToken.current = deviceData.access_token;
        apiToken.current = deviceData.id;
        console.log(deviceData);
      };
    
      useEffect(() => {
        getData().then(response => {
          fetchData();
        });
      }, []);
    
      const renderItem = ({
        item,
        index,
      }: {
        item: IListItem,
        index: number,
      }): React.ReactElement => (
        <ListItem
          title={`${item.name}`}
          description={`${item.address + ', ' + item.number + ' - ' + item.city}`}
          onPress={() =>
            navigation.navigate('Details', {
              siteId: item.id,
            })
          }
        />
      );
    
      return (
        <SafeAreaView style={GlobalStyles.safeArea}>
          <TopNavigation
            title={evaProps => (
              <Text style={GlobalStyles.headerTitle}>Cantieri</Text>
            )}
            alignment="center"
          />
          <Divider />
          <FlatList
            data={data}
            refreshControl={
              <RefreshControl refreshing={isRefreshing} onRefresh={fetchData} />
            }
            ItemSeparatorComponent={Divider}
            renderItem={renderItem}
          />
        </SafeAreaView>
      );
    };
    

    Because refs don’t cause re-renders this should work for what you are trying to do without causing multiple renders. Here is the documentation on useRef https://react.dev/reference/react/useRef .

    Also Here is a link to the Caveats of useState that explain a little better what I was saying about updating state on next render. https://react.dev/reference/react/useState#setstate-caveats

    Login or Signup to reply.
  3. Instead of this code

    useEffect(() => {
        getData().then(response => {
          fetchData();
        });
      }, [apiToken, userId]);
    
    

    you need to split this into 2 useEffects() as following:

    export const SitesScreen = ({navigation}) => {
      ....
      useEffect(() => {
        getData();
      }, []);
    
      useEffect(() => {
        if (apiToken && userId) { // check validation of apiToken && userId
          fetchData();
        }
      }, [apiToken, userId]);
      ...
    }
    

    first useEffect() is called at the initial step, and the second one is called whenever apiToken and userId are changed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search