4

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.

Stephen A. Lizcano
  • 442
  • 1
  • 7
  • 17
  • 1
    Currently, setState in useState hook doesn't support callback. But you can use a useEffect hook to do 'after render' callbacks and coordinate with the current state value. – Tien Duong Jul 22 '19 at 02:59
  • Yep understood, but as mentioned above I have a unique case where I am using the value immediately afterwards to save it in an API call. – Stephen A. Lizcano Jul 22 '19 at 03:03
  • Maybe this answer can help you https://stackoverflow.com/questions/53446020/how-to-compare-oldvalues-and-newvalues-on-react-hooks-useeffect. – Tien Duong Jul 22 '19 at 03:11

2 Answers2

1

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();
    }

});
Chuksy
  • 272
  • 2
  • 6
  • This doesn't work if I understand correctly, since that way it will either fire on every change of the dependency, or on mount. – Stephen A. Lizcano Jul 22 '19 at 03:13
  • Correct—this will run on every render, which would not be great for your api. You need a dependency array to keep it from getting out of hand. – shadymoses Jul 22 '19 at 03:17
  • You can check if the note object is empty before calling the saveNote function. @stephenlizcano – Chuksy Jul 22 '19 at 03:26
1

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.

shadymoses
  • 3,273
  • 1
  • 19
  • 21
  • Thanks for the detailed comment. Just updated my code with some more details. Basically, I have the results of the useDictation hook fed into my component, and if there are partial results still returned before the array, I'd like to add it to the note. I have multiple hooks with the saveAPICalls baked inside of them, so if I have to update the functions to take in the note vs accessing it inside the hook, I have to update a lot of hooks (we have a lot of note types). – Stephen A. Lizcano Jul 22 '19 at 03:31
  • That would qualify as a compelling reason to define it inline :) – shadymoses Jul 22 '19 at 03:32
  • hehe :) Unfortunately I can't make an API call for every state update on the note, it would kill my AWS bills :) – Stephen A. Lizcano Jul 22 '19 at 03:34