0

I wrote a custom hook, it maintains a state and exports that variable and it is used in a component. Inside the hook, the variable is never updated, but inside the component it is. Here is some short hand code to demonstrate it.

// useCustomHook.js
export default useCustomHook => {
    const [ quesiton, setQuestion ] = useState("")
    useEffect(() => {
        emitter = new NativeEventEmitter(nativeModule)
        emitter.addListener('eventUpdate', e => {
            setQuestion(e) // e == "testing"
            console.log(question) // question == ""
        })
        emitter.addListener('eventEnd', e => {
            setQuestion(e) // e == "testing"
            finishEvent()
        })
        const finishEvent = () => {
            console.log(question) // question == ""
        }
    }, [])
    return [ question ]
}

// component.js
export default component = props => {
    const [ question ] = useCustomHook()
    return (
        <Text>{question}</Text> // question == "testing"
    )
}

Every time the update event happens, question is updated in the component. But in that same function, question is always an empty string. I can't make sense of what is happening here, it must be some weird behavior of the useState setter that I don't understand.

To add some clarification, as for the console.log in the update event, I am not expecting it to output the correct value in that call. It is subsequent calls where the value is never updated. The update function will get called 20-30 times as new values are sent to it before the end event is triggered. This can happen over the span of 20-30 seconds. The value that is consoled out is always an empty string.

David Karasek
  • 348
  • 3
  • 16
  • Does this answer your question? [useState set method not reflecting change immediately](https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately) – Emile Bergeron Dec 12 '19 at 01:02
  • @EmileBergeron I will dig deeper into thiis, but on the surface it doesnt seem to. The component consuming `question` is showing the proper content, but the function calling the setter is showing `question` in its original state. So I call setQuestion, the component shows the proper value, but even subsequent calls show the incorrect init value – David Karasek Dec 12 '19 at 02:59
  • 1
    `setQuestion(e)` is async, so it does not change the value of `question` immediately. Also, hooks memoize most of the stuff you pass to them, so the function is not recreated each time, meaning that `question` is only ever the initial value when the function was first created. If you want the old value, use the _updater_ function of the setter: `setQuestion(oldQuestion => { /* do whatever with it */ return newQuestionValueHere })` – Emile Bergeron Dec 12 '19 at 03:05
  • From the [docs](https://reactjs.org/docs/hooks-effect.html)...might help a bit. Scroll to the bottom where the `Note` is just above the `Next Steps` section – Michael Mayo Dec 12 '19 at 06:51
  • @EmileBergeron I do know that it is async, I had this exact same pattern when using another library. However, it was buggy and unmaintained so I wrote my own native library. I did follow the exact same pattern used in the other for the interface. the update event could be called 30+ times over the period of 20-30 seconds before the end event is called. And inside the functions, question is never updated. However, the version in the component is. So I don't know why `setQuestion` would be called and the `question` in the component is updated, but the `question` inside the hook is not. – David Karasek Dec 12 '19 at 12:45
  • @MichaelMayo I am using it as a constructor here so I only want the effect to run once, but just for prosperity I tried adding question to the array so it would watch it, and it did have the correct value in the update event, but still did not in the finishEvent function. – David Karasek Dec 12 '19 at 14:07
  • 2
    @EmileBergeron gave you the answer. Your useEffect captured `question` when it was in its initial empty string state and you are always logging that value. Put a console.log statement outside the useEffect callback and you will see fewer calls and with better behavior. – Doug Coburn Dec 12 '19 at 14:58
  • 1
    @EmileBergeron Ok, I get what the issue is now. It is the memoized copy that is being used within the useEffect. I moved the function definitions outside the useEffect and things are working as expected. The weird thing is, I am pretty sure I did this yesterday prior to posting this question. But I must have had something not quite the same going on. Thank you for your help! – David Karasek Dec 12 '19 at 15:42

1 Answers1

0

Thanks to Emile Bergeron and Doug Coburn for pointing me in the right direction. The value is being memoized inside the useEffect and thus not being updated within the function. Here is how I fixed it using the example.

export default useCustomHook => {
    const [ quesiton, setQuestion ] = useState("")
    emitter = new NativeEventEmitter(nativeModule)
    const eventUpdate = e => {
        setQuestion(e) 
        console.log(question) 
    }
    const eventEnd = e => {
        setQuestion(e) 
        finishEvent()
    }
    const finishEvent = () => {
        console.log(question) 
    }
    useEffect(() => {
        emitter.addListener('eventUpdate', eventUpdate)
        emitter.addListener('eventEnd', eventEnd)
    }, [])
    return [ question ]
}

I was pretty sure I had tried this, but I guess I still had something wrong when I did before. I put the function declarations within the useEffect as I had read somewhere that it is better to do this. So if anyone has any additional suggestions on best practices I would be glad to hear them.

David Karasek
  • 348
  • 3
  • 16