Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.useCallback not updating with dependency

I'm using the following component to submit comments in my app:

const App = () => {
    const [text, setText] = React.useState("");

    const send = React.useCallback(() => {
        setText("");
        console.log("sending", text);
    }, [text]);

    React.useEffect(() => {
        const handler = e => {
            switch (e.keyCode) {
                case 13: // enter
                    if (e.shiftKey) {
                        e.preventDefault();
                        send();
                    }
                    break;
            }
        }
        
        document.addEventListener("keydown", handler);
        return () => document.removeEventListener("keydown", handler);
    }, []);

    return <div className="App">
        <textarea
            className="App__text"
            value={text}
            onChange={e => {
                setText(e.target.value);
            }} />
        <button className="App__send" onClick={send}>send</button>
    </div>;
};

working demo here

It's a simple text field and button. When either the button, or shift-enter are pressed, the text in the text field is sent to the server (here we just console.log it).

The button works fine - enter "hello world" (or whatever) press the button, and the console will say hello world.

Shift-enter, however, always prints an empty string.

I'm guessing I'm misunderstanding useCallback. As I understand it, useCallback wraps your function. When one of the dependencies change, React swaps out your function without changing the wrapper function so it's still an up-to-date reference wherever it's used. Given that the send being called in useEffect appears to have the initial value of text in scope, but the one used in the onClick of the button has the newest, my assumptions seem incorrect.

I've also tried adding text as a dependency of the useEffect but

  • I don't want to remove/create/add the listener on every keystroke and
  • It's still missing the last character pressed anyway.

How can I keep a current version of my send function inside of another callback?

like image 350
Sandy Gifford Avatar asked Mar 14 '26 00:03

Sandy Gifford


1 Answers

You are missing send in your dependencies, take a look at this updated code:

React.useEffect(() => {
  const handler = e => {
    switch (e.keyCode) {
      case 13: // enter
        if (e.shiftKey) {
          e.preventDefault();
          send(); // This will always be called an older version, instead of updated one
        }
        break;
    }
  }

  document.addEventListener("keydown", handler);
  return () => document.removeEventListener("keydown", handler);
}, [send]); // You need to put send here, since it is part of this component, and needs to be updated

The reason It works with it, is because send function is memoised as well:

const send = React.useCallback(() => {
  setText("");
  console.log("sending", text);
}, [text]);

so you need to make sure to update to its newer version, with its newer text (which never happened, thats why you got no text in your SHIFT+ENTER)

EDIT: Upon further investigation, it seems to me that the biggest problem was out-of sync text and listener handler.

I have modified the code to work, by removing the listener alltogether, and using onKeyDown prop directly on textarea. Take a look at this working codepen:

https://codepen.io/antonioerda/pen/zYqYWgx

like image 145
Antonio Erdeljac Avatar answered Mar 16 '26 13:03

Antonio Erdeljac



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!