问题
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);
回答1:
Take a look at this lib: https://www.npmjs.com/package/use-debounce
Here's an example of how to use it:
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000);
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>Actual value: {text}</p>
<p>Debounce value: {value}</p>
</div>
);
}
回答2:
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.
回答3:
You can try this.
import React, { useState, useCallback, useRef, useEffect } from 'react';
import _ from 'lodash';
function usePrevious(value: any) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const DeboucnedInput = React.memo(({ value, onChange }) => {
const [localValue, setLovalValue] = useState('');
const prevValue = usePrevious(value);
const ref = useRef();
ref.current = _.debounce(onChange, 500);
useEffect(() => {
if (!_.isNil(value) && prevValue !== value && localValue !== value) {
setLovalValue(value);
}
}, [value]);
const debounceChange = useCallback(
_.debounce(nextValue => {
onChange(nextValue);
}, 1000),
[]
);
const handleSearch = useCallback(
nextValue => {
if (nextValue !== localValue) {
setLovalValue(nextValue);
debounceChange(nextValue);
}
},
[localValue, debounceChange]
);
return (
<input
type="text"
value={localValue}
onChange={handleSearch}
/>
);
});
来源:https://stackoverflow.com/questions/59383595/how-to-debounce-a-controlled-input