简体   繁体   中英

How to await a setState call finishing when using useState hook that immediately needs that state to make an API call?

I have a voice dictation custom hook, along with a separate custom hook that appends the dictation results to an object that stores "Note" values.

If the user clicks save too early, there are still Partial Results that I need to append right before an API call that saves the Note.

My code looks like this

function NoteDictation(props) {

  const [
    results,
    partialResults,
    error,
    toggleRecognizing,
    speechHasStarted,
  ] = useDictation();

    const [note, setNote, saveNoteAPICall, updateNoteAPICall] = useNote({})
    //Use Note is a custom hook that has that certain type of note's properties built in (they're medical notes, and we have a custom hook for each type of note).



    function handleSavePress(){

      if(partialSpeechResults){
        //If the dictation software hasn't returned a final result, 
        //append the partialSpeechResults
        setNote({...note, text: note.text +  partialSpeechResults})
      }


      //SaveNote does not use the value set above if there are partial results.
      saveNote()
    }

    return (
     <View>
       <NoteContents note={note} results={results} partialResults={partialResults} />
       <Button onPress={handleSavePress> />
    </View>
    )

}

Problem is, the SaveNote is being called and is using the old state of the note...the setting of the state is not completing on time.

I can't seem to use a useEffect hook here to monitor the change, since I am calling the API to save the note right away and it's accessing the note state when saving.

What is the best way to handle this? Thank you.

Try the useEffect hook:

EDIT: Since it runs on first render you want to be sure the note object is not empty before saving

useEffect(() => {

    if(Object.keys(note).length !== 0){

        saveNote();
    }

});

EDIT

Given the updated code, you should be able to handle it very similarly to how I outlined in my original answer:

// will run on mount and whenever note changes
useEffect(() => {
  // skip first run (what you check depends on your initial note value)
  if (Object.keys(note).length) {
    saveNoteAPICall()
  }
}, [note])

function handleSavePress(){
  if(partialSpeechResults){
    // If the dictation software hasn't returned a final result, 
    // append the partialSpeechResults
    // and let if fall into useEffect when note updates
    setNote({...note, text: note.text +  partialSpeechResults})
  } else {
    // run immediately if not partial
    saveNoteAPICall()
  }
}

The key difference is that you only call saveNote inside your press handler if you don't have a partial result. That way you don't get incomplete saves. If you setNote , it will drop into your useEffect and save with the right value.

If this is a common pattern for handling these notes, it might make sense to move this logic into your useNote hook.


Original answer

Since you're using useState for your note value, you should be able to handle this with useEffect . Values from useState are immutable, so they work great as inputs for effects hooks. Move your call to saveNote() outside of handleSavePress and into useEffect :

const [note, setNote] = useState({})

// ...Other misc code

// this will run on first execution,
// and then any time the value of note changes
useEffect(() => {
  // skip first run
  if (Object.keys(note).length) {
    saveNote(note)
  }
}, [note])

function handleSavePress(){
  if (partialSpeechResults) {
    // If the dictation software hasn't returned a final result, 
    // append the partialSpeechResults
    setNote({ ...note, text: note.text +  partialSpeechResults })
  }
}

If, for some reason, your saveNote function is defined inside this component, I would suggest moving it outside the component and passing it note as an argument so you can be sure useEffect will run only when you want it to. If there is some compelling reason why you need to define saveNote inside the component, then you should define saveNote with useCallback and change your useEffect function to key off changes to that:

const [note, setNote] = useState({})

// ...Other misc code

// this function will only change when note changes
const saveNote = useCallback(() => {
  // whatever api call you need to run here
  // that uses the note value in scope
}, [note])

// this will run on first execution,
// and then any time the value of note (and thus saveNote) changes
useEffect(() => {
  // skip first run
  if (Object.keys(note).length) {
    saveNote()
  }
}, [saveNote, note])

function handleSavePress(){
  if (partialSpeechResults) {
    // If the dictation software hasn't returned a final result, 
    // append the partialSpeechResults
    setNote({ ...note, text: note.text +  partialSpeechResults })
  }
}

It's difficult to determine exactly where things might be going wrong without seeing a more complete code example. The // ...Other misc code section of your example is kind of important, particularly where and how you're defining saveNote .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM