score:4

Accepted answer

You get this error because you try to .persist() the event inside the debounce's timeout. When the timeout invokes the callback, the synthetic event was already released. So you'll have to persist the event outside of the debounce.

However, your idea has another problem. Since the textbox is a controlled component, debouncing the updated value, would cause the textbox to render (part of) the text only after the used stopped typing.

To prevent that you need to update the state for the controlled element immediately, and debounce the update for the display state (or the redux action dispatch).

For example:

class TextArea extends React.Component {
  constructor(props){
    super(props);
    this.state = { foo: '', controlled: '' }
  }
  
  updateFoo = _.debounce((value) => { // this can also dispatch a redux action
    this.setState({foo: value});
  }, 300);
  
  handleInputChange = (e) => {
    const value = e.target.value;
    
    this.setState({
      controlled: value
    });
    
    this.updateFoo(value);
  }

  render() {
    return (
      <div>
       <textarea onChange={ this.handleInputChange }
       value={this.state.controlled} />
       <p id="preview">{this.state.foo}</p>
      </div>
    );
  }
}


ReactDOM.render(
  <TextArea />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>

score:0

I struggled a lot with this and wasn't able to achieve a satisfying result by myself. Eventually, I used https://github.com/nkbt/react-debounce-input which works perfectly and is much simpler than my previous failed attempts.

  /**
   * Updates the current node "text" value.
   *
   * @param event
   */
  const onTextInputValueChange = (event: any) => {
    const newValue = event.target.value;
    patchCurrentNode({
      data: {
        text: newValue,
      },
    } as InformationNodeData);
  };
    

  <DebounceInput
          element={'textarea'}
          debounceTimeout={500}
          placeholder={'Say something here'}
          onChange={onTextInputValueChange}
          value={node?.data?.text}
        />

patchCurrentNode writes to my Recoil store.

The DebounceInput component handles an internal state to display the latest value, while only updating the value in the store only once in a while.

Implementation isn't specific to Recoil, and would likely work like a charm using Redux.

score:0

Functional component:

import React, { useState, useMemo } from "react";
import debounce from "lodash.debounce";

export default function TextArea() {
  const [foo, setFoo] = useState("");
  const [controlled, setController] = useState("");

  const updateFoo = useMemo(
    () =>
      debounce((value) => {
        // this can also dispatch a redux action
        setFoo(value);
      }, 1000),
    []
  );

  const handleInputChange = (e) => {
    const value = e.target.value;

    setController(value);

    updateFoo(value);
  };

  return (
    <div>
      <textarea onChange={handleInputChange} value={controlled} />
      <p id="preview">{foo}</p>
    </div>
  );
}

score:1

The other answer already covered the problem with persisting the event. For the input debouncing aspect, you may want to read my blog post Practical Redux, Part 7: Form Change Handling. In that post, I show a reusable component that can handle debouncing text input updates for the rest of the application, while allowing them to re-render immediately with the current value.


Related Query

More Query from same tag