Sandy Gifford
Sandy Gifford

Reputation: 8136

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

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

Upvotes: 12

Views: 20683

Answers (2)

Chiemezie Ucheaga
Chiemezie Ucheaga

Reputation: 1

Two things to make sure you do when using the useCallback hook:

  1. make sure to add the list of all the needed dependencies that are external to the method are listed.
  2. if inside this method with the usecallback you call another method that you created using the usecallback, include the dependencies there in this one as well.

Upvotes: 0

Antonio Erdeljac
Antonio Erdeljac

Reputation: 3244

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

Upvotes: 11

Related Questions