Search code examples
javascriptreactjsreact-hooksuse-effect

Why doesn't my useEffect go into an infinite loop when modifying it's dependency?


Code

PrizeHistory.js

...

const PrizeHistory = () =>  {
  const [history, setHistory] = useState([]);

  useEffect(() => {

    async function getHistory () {
      try {
        let result = await sqliteInterface.getAllPrizes(db, prizeTable);
        result == null ? console.log("res is null") : setHistory(result);
        console.log("result" + result);
        console.log("history" + history);
      } catch (err) {
        console.log(err);
      }
    }

    getHistory();
  }, [history])

  return (
    <View style={styles.body}>
      <Text style={styles.text}>Prize History</Text>
    </View>
  );
}

getAllPrizes

getAllPrizes(db, tableName) {
    return new Promise((resolve, reject) => {
      db.transaction((tx) => {
        tx.executeSql(
          `SELECT * FROM ${tableName};`, 
          [],
          (tx, res) => {
            let len = res.rows.length;
            let results = [];  

            if(len > 0) {
              for(i = 0; i < res.rows.length; i++) {
                results.push(res.rows.item(i));
              }
            } 
            console.log("resolving promise now");
            resolve(JSON.stringify(results));
          },
          (error) => {
            reject(Error(`SQLite getAllPrizes: failed to get '*' from ${tableName}: ` + error));
          }
        );
      }); 
    });
  }

The goal

When the page is mounted, set the history state to the data in the database. I initially tried this:

  useEffect(() => {

    async function getHistory () {
      try {
        let result = await sqliteInterface.getAllPrizes(db, prizeTable);
        result == null ? console.log("res is null") : setHistory(result);
        console.log("result" + result);
        console.log("history" + history);
      } catch (err) {
        console.log(err);
      }
    }

    getHistory();
  }, [])

but it never set my history variable correctly. I just got an empty array (which is what I inistially set the history state to). I expected the history variable to be equal to the result variable in the history console.log(), but it's not.

So I changed the useEffect() to this:

  useEffect(() => {

    async function getHistory () {
      try {
        let result = await sqliteInterface.getAllPrizes(db, prizeTable);
        result == null ? console.log("res is null") : setHistory(result);
        console.log("result" + result);
        console.log("history" + history);
      } catch (err) {
        console.log(err);
      }
    }

    getHistory();
  }, [history])

This code changes the history variable correctly, but I expected it to go into an infinite loop... but it didn't? Here's my logic

  1. useEffect() has [history] as dependency
  2. onMount, history is set to default value of []
  3. useEffect() sees history is set and begins running
  4. useEffect() resets history
  5. useEffect() should start again because it reset the history variable itself
  6. Repeat 3-5 infinitely...

But it does not repeat infinitely...

My Questions

  1. Why is useEffect() not running infinitely when [history] is set as its dependency
  2. Why is useEffect() not setting my history variable correctly on mount when it has [] as it's dependency.

Solution

  • The answer to your 1 question is: history does not change after first set because your getAllPrizes returns always the same value so the useEffect is triggered only once (on mount).

    For the 2 question, the problem is that you are trying to log the history value right after a call to setHistory, which is async. If you want to log history each time it is updated you have to do something like:

    useEffect(() => {
      console.log(history)
    }, [history])