I’m currently struggling with react inputs and debounce from lodash.
Most of the time when I have a form I also have an edit option, so I need a controlled component to fill back the inputs using value={state["targetValue"]}
so I can fill and edit the field.
However, if the component is controlled debounce isn’t working. I made a simple example on CodeSandbox: https://codesandbox.io/embed/icy-cloud-ydzj2?fontsize=14&hidenavigation=1&theme=dark
Code:
import React, { Component } from "react"; import ReactDOM from "react-dom"; import { debounce } from "lodash"; import "./styles.css"; class App extends Component { constructor(props) { super(props); this.state = { name: "", title: "", editMode: false }; this.debouncedEvent = React.createRef(); } debounceEvent(_fn, timer = 500, options = null) { this.debouncedEvent.current = debounce(_fn, timer, options); return e => { e.persist(); return this.debouncedEvent.current(e); }; } componentWillUnmount() { this.debouncedEvent.current.cancel(); } onChangeValue = event => { const { name, value } = event.target; this.setState(() => { return { [name]: value }; }); }; onRequestEdit = () => { this.setState({ name: "Abla blabla bla", editMode: true }); }; onCancelEdit = () => { if (this.state.editMode) this.setState({ name: "", editMode: false }); }; onSubmit = event => { event.preventDefault(); console.log("Submiting", this.state.name); }; render() { const { name, editMode } = this.state; const isSubmitOrEditLabel = editMode ? `Edit` : "Submit"; console.log("rendering", name); return ( <div className="App"> <h1> How to debounce controlled input ?</h1> <button type="button" onClick={this.onRequestEdit}> Fill with dummy data </button> <button type="button" onClick={this.onCancelEdit}> Cancel Edit Mode </button> <div style={{ marginTop: "25px" }}> <label> Controlled / Can be used for editing but not with debounce </label> <form onSubmit={this.onSubmit}> <input required type="text" name="name" value={name} placeholder="type something" // onChange={this.onChangeValue} onChange={this.debounceEvent(this.onChangeValue)} /> <button type="submit">{isSubmitOrEditLabel}</button> </form> </div> <div style={{ marginTop: "25px" }}> <label> Uncontrolled / Can't be used for editing </label> <form onSubmit={this.onSubmit}> <input required type="text" name="name" placeholder="type something" onChange={this.debounceEvent(this.onChangeValue)} /> <button type="submit">{isSubmitOrEditLabel}</button> </form> </div> </div> ); } } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
Advertisement
Answer
So… Apparently, there’s no solution. the input takes the value from the state. While debounce prevents the state to trigger.
I made a workaround using ReactDOM.
import ReactDOM from "react-dom"; export const setFormDefaultValue = (obj, ref) => { if (ref && !ref.current) return; if (!obj || !obj instanceof Object) return; const _this = [ ...ReactDOM.findDOMNode(ref.current).getElementsByClassName("form-control") ]; if (_this.length > 0) { _this.forEach(el => { if (el.name in obj) el.value = obj[el.name]; else console.error(`Object value for ${el.name} is missing...`); }); } };
and then the use:
this.refForm = React.createRef(); setFormDefaultValue(this.state, refForm)
This way I can fill my form with the state default value and continue using debounce.