How to debounce a controlled input?

允我心安 提交于 2021-02-05 07:50:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!