4

I am executing useEffect() to update a state with JSON data. However the fetch request sometimes fails, so I want to re-execute the useEffect hook if that happens:

...
import React, {useState, useEffect} from 'react';
import {getJsonData} from './getJsonData';

const myApp = () => {
    var ErrorFetchedChecker = false;
    const [isLoading,setIsLoading] = useState(true);
    const [data,setData] = useState(null);

    const updateState = jsonData => {
      setIsloading(false);
      setData(jsonData);
    };

    useEffect(() => {
      //console.log('EXECUTING');
      getJsonData().then(
        data => updateState(data),
        error => {
          Alert.alert('DATA FETCHING ERROR !', 'Refreshing ?...');
          ErrorFetchedChecker = !ErrorFetchedChecker;
          //console.log('LOG__FROM_CountriesTable: Executed');
        },
      );
    }, [ErrorFetchedChecker]);//Shouldn't the change on this variable
                              //be enough to re-execute the hook ? 

    return (
      <View>
        <Text>{state.data.title}</Text>
        <Text>{data.data.completed}</Text>
      </View>
    );
}

Here's the getJsonData() function just in case:

export async function getJsonData() {
  try {
    let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    let responseJson = await response.json();
    return responseJson;
  } catch (error) {
    throw error;
    // Also, is this the correct way to handle the error ?
    // As the Alert in useEffect goes off either ways.
    // If not, advise me on how the error should be handled.
  }
}
usersina
  • 1,063
  • 11
  • 28
  • 1
    useEffect will run again if your react state changes, it will not run again if your local variable changes. Convert ErrorFetchedChecker to react state. – Nayan shah Mar 26 '20 at 17:44
  • props also cause a re-render, so useEffect will run again on prop changes – usersina Mar 26 '20 at 18:09
  • It is true, but here the lifetime of ErrorFetchedChecker is very less, as react doesn't store other variable values in their stack. That is why useState is used. – Nayan shah Mar 26 '20 at 18:22

2 Answers2

6

This will work

const myApp = () => {
    const [errorFetchedChecker, setErrorFetchedChecker] = useState(false);
    const [isLoading,setIsLoading] = useState(true);
    const [data,setData] = useState(null);

    const updateState = jsonData => {
      setIsloading(false);
      setData(jsonData);
    };

    useEffect(() => {
      //console.log('EXECUTING');
      getJsonData().then(
        data => updateState(data),
        error => {
          Alert.alert('DATA FETCHING ERROR !', 'Refreshing ?...');
          setErrorFetchedChecker(c => !c);
          //console.log('LOG__FROM_CountriesTable: Executed');
        },
      );
    }, [errorFetchedChecker]);

    return (
      <View>
        <Text>{state.data.title}</Text>
        <Text>{data.data.completed}</Text>
      </View>
    );
}
Nayan shah
  • 543
  • 3
  • 8
  • Thanks, a neat solution. Can I add a timeout on the execution though? So the useEffect re-executes each 2 seconds for example as it makes the application a bit laggy. – usersina Mar 26 '20 at 18:00
  • 1
    Yes, you can put timeout inside useEffect but don't forget clearTimeout. https://stackoverflow.com/questions/53090432/react-hooks-right-way-to-clear-timeouts-and-intervals – Nayan shah Mar 26 '20 at 18:06
  • This worked for me as well -- very nicely done. Question: what is the (c => !c) doing precisely? I understand the result is changing the state of errorFetchedChecker from false to true but I haven't seen this pattern before within a useState hook, nor is "c" referenced elsewhere. Additionally, why does this work even with repeated attempts -- intuitively I would think that multiple calls (in the instance of multiple failures) would switch the error state back and forth, but that doesn't seem to be what's happening. – Chris Perry Mar 24 '21 at 22:45
  • @ChrisPerry the code is passing an arrow function to `setErrorFetchedChecker`. Basically, you can pass a function that takes the current value as the first parameter and returns the new value. Here it's being used to toggle the `errorFetchedChecker` value. You can [read more in the docs](https://reactjs.org/docs/hooks-reference.html#functional-updates). – TheBosZ Apr 15 '21 at 17:09
0
import React, { useState, useRef, useEffect } from "react";
import { Text, View, TextInput } from "react-native";

const App = () => {
  var ErrorFetchedChecker = false;
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState(null);
  const updateState = (jsonData) => {
    setIsLoading(false);
    setData(jsonData);
  };

  useEffect(() => {
    //console.log('EXECUTING');
    getJsonData()
      .then((data) => {
        console.log("1. Successful, just received the data from our promise");
        updateState(data);
        console.log("2. We set our data because we received it successfully");
        return { alreadySet: true };
      })
      .catch((e) => {
        console.log("1. We failed to gather data in our initial promise");
        console.log("2. Attempting to rerun initial promise");
        return getJsonData();
      })
      .then((data) => {
        if (data.alreadySet) {
          console.log(
            "3. Did not attempt to retry because we are already successful"
          );
        } else {
          console.log("3. Our second attempt succeeded");
          updateState(data);
          console.log("4. Set our data on our second attempt");
        }
      })
      .catch((e) => {
        console.log("3. Both attempts have failed");
      });
  }, []); //Shouldn't the change on this variable
  //be enough to re-execute the hook ?

  return (
    <View>
      <Text>{data ? <Text>{data.title}</Text> : null}</Text>
    </View>
  );
};

export async function getJsonData() {
  try {
    let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    let responseJson = await response.json();
    return responseJson;
  } catch (error) {
    throw error;
    // Also, is this the correct way to handle the error ?
    // As the Alert in useEffect goes off either ways.
    // If not, advise me on how the error should be handled.
  }
}
export default App;
  • Do you mean replacing [ErrorFetchedChecker] with [getJsonData()] ? In doing so, I get 2 warnings: "Possible Unhandled Promise Rejection": 1.TypeError: Network request failed and 2.SyntaxError: JSON Parse Error unrecognized token – usersina Mar 26 '20 at 17:44
  • Can you check above screenshot? Tried to use my phone hahah.... Let me know I can hop on my computer if that doesn't work – Anthony Moon Beam Toorie Mar 26 '20 at 17:46
  • Still no luck, only executes once – usersina Mar 26 '20 at 17:52
  • The link you've sent only has the default react native View – usersina Mar 26 '20 at 18:31
  • Waiting for your update, although I get what you mean by x-times. Nice thought! – usersina Mar 26 '20 at 18:55
  • Seems like there is some discussion on this subject here : https://stackoverflow.com/questions/38213668/promise-retry-design-patterns Whilst I wanted to give you a solid answer, it appears that my answer is not scalable. If you want to address this issue, follow that link and try to implement their design pattern for retries. It seems that as for now, it might be best to use the other gentleman's suggestion, and keep track of the number of attempts via a useRef variable. It's actually not that bad :-)... Sorry to disappoint you and good luck out there. – Anthony Moon Beam Toorie Mar 26 '20 at 19:59
  • Nayan shah's answer seems where you want to go for now. I will keep trying to search on my end for a scalable solution that is also SIMPLE and clean. I will get back to you on my findings. Best-AT – Anthony Moon Beam Toorie Mar 26 '20 at 20:02
  • Nice solution! Even if not scalable, it should work for small requests. Also, I didn't even know useRef existed so that's a plus. Thanks for sharing! – usersina Mar 26 '20 at 21:08
  • Anytime :). My pleasure – Anthony Moon Beam Toorie Apr 06 '20 at 01:58
  • Hey Anis Benna, I updated the response to include a cleaner version of the solution we discussed. I hope if you decide to use our solution, you update the previous solution to this one, as it will remove a moot reference variable we had before using useRef. In this scenario, it may not even be required, and adhering to clean coding practices, we must try not to create unnecessary variables in order to "fix" something, as it will almost always lead to more complex code. – Anthony Moon Beam Toorie Apr 08 '20 at 19:18