'How to await state change in React Native?

I have spent the whole day searching through similar questions etc. but to no avail and I am losing my mind.

I am building a "translate app" using an API. In my Home functional component which is one of three in my bottom tab navigator, I have these states:

const [from, setFrom] = useState({language:'German', code:'ge_GE'});
const [to, setTo] = useState({language: 'English', code: 'en_GB'});
let [input, setInput] = useState("");
const [output, setOutput] = useState("");
const [favorites, setFavorites] = useState([])

There's a <TextInput> with onChange handler to setInput. I call fetch() as follows:

const fetch = () =>  {
    axios.request(options).then(function (response) {
      setOutput(response.data.result);
    }).catch(function (error) {
      console.error(error);
      setOutput("");
    });
  } 

const options = {
  method: 'POST',
  url: 'https://lingvanex-translate.p.rapidapi.com/translate',
  headers: {
    'content-type': 'application/json',
    'X-RapidAPI-Host': 'lingvanex-translate.p.rapidapi.com',
    'X-RapidAPI-Key': 'xxxx-xxxx-xxxx-xxxx'
  },
  data: '{"from":"'+ from.code +'","to":"'+ to.code +'","data":"'+ input +'","platform":"api"}'
  };

The output then updates in my UI and the wanted result in UI is achieved.

THE PROBLEM IS: If I then use a button to save this translation to favorites with saveHandler on onPress prop like this:

function saveHandler(){
  const newFaves = [...favorites,{phrase: input, translation: output, source: from.code, target: to.code, key: favorites.length + 1 }];
    setFavorites(newFaves);
  }

After inspecting the latest favorite state:

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

I see that all the states have held their previous values while being supplied in saveHandler.

I have seen similar issues on SO, people using second callback arguments, but I keep getting told by expo console that useState does not support it. I am not really trying to turn to class components as I am absolutely lost after trying it. I believe there must be a way to do this. I ask you kind souls of SO, for your helping hand :(

edit: I understand such thing is achievable through useEffect, but I did not know how can I use this useEffect to "update its own dependency"... I think I figured out a way but it seems it might cause performance issues. After creating a non-state variable counterpart to every state, I can now do

useEffect(() => {
  hardOutput = output
},[output])

with saveHandler using the assigned non-state variable instead of state itself. Since I need to achieve this for four states, this seems to be lagging up the app, but is working somehow.

function saveHandler(){
  const newFaves = [...favorites,{phrase: hardInput, translation: hardOutput, source: hardFrom.code, target: hardTo.code, key: favorites.length + 1 }];
    setFavorites(newFaves);
  }

edit 2: I found this article on stale closures https://dmitripavlutin.com/react-hooks-stale-closures/ and tried using the functional setState((previousState) => previousState = newState) but still got the same results.

REPREX:

import { useState, useEffect} from 'react';
import {  View, Text, Button, TextInput} from 'react-native';

const axios = require("axios");

function Home() {
    const [from, setFrom] = useState({language:'Slovenčina', code:'sk_SK', key: 1});
    const [to, setTo] = useState({language: 'Angličtina', code: 'en_GB', key: 2});
    let [input, setInput] = useState("");
    const [output, setOutput] = useState('')
    const [favorites, setFavorites] = useState([{
    phrase: "Hello",
    translation: "Dobrý deň",
    source: "en_EN",
    target: "sk_SK",
    key: "1"
  }]);
 
const fetch = () =>  {
    axios.request(options).then(function (response) {
    setOutput(response.data.result);
    }).catch(function (error) {
      console.error(error);
      setOutput("");
    });
  } 

let saveButton = [];
const options = {
  method: 'POST',
  url: 'https://lingvanex-translate.p.rapidapi.com/translate',
  headers: {
    'content-type': 'application/json',
    'X-RapidAPI-Host': 'lingvanex-translate.p.rapidapi.com',
    'X-RapidAPI-Key': 'xxxx-xxxx-xxxx-xxxx'
  },
  data: '{"from":"'+ from.code +'","to":"'+ to.code +'","data":"'+ input +'","platform":"api"}'
  };

function inputHandler(newText) {
  setInput(newText);
  setOutput("");
}

useEffect(() => {
    saveButtonHandler()
},[output])

function saveButtonHandler() {
saveButton= [];
saveButton.push(<TouchableOpacity onPress={saveHandler}>
  <Text>Save</Text>
</TouchableOpacity>);
}

function saveHandler(){
  const newFaves = [...favorites,{phrase: input, translation: output, source: from.code, target: to.code, key: favorites.length + 1 }];
    setFavorites(newFaves);
  }

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

return (
<TextInput 
value={input} 
onChangeText={inputHandler}/>
<Button title="Translate" onPress={translateHandler}>
{saveButton}
);
}

Note: Functional component above is nested inside a tab navigation component nested inside main App function in App.js



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source