1
votes

Comment faire rebondir une entrée contrôlée?

Je suis actuellement aux prises avec des entrées de réaction et anti-rebond de lodash. La plupart du temps, lorsque j'ai un formulaire, j'ai également une option d'édition, donc j'ai besoin d'un composant contrôlé pour remplir les entrées en utilisant value = {state ["targetValue"]} afin que je puisse remplir et modifiez le champ.

Cependant, si le composant est contrôlé, l'anti-rebond ne fonctionne pas. J'ai fait un exemple simple sur 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);


2 commentaires

Qu'est-ce que vous essayez de rebondir exactement?


J'essaie de supprimer mon champ de saisie afin qu'ils ne déclenchent pas un nouveau rendu sur setState tant que l'utilisateur n'a pas terminé de taper. Dans l'exemple que j'ai publié, la deuxième entrée fonctionne comme prévu, mais elle ne peut pas être utilisée en mode d'édition.


3 Réponses :


1
votes

Jetez un œil à cette bibliothèque: https://www.npmjs.com/package/ use-debounce

Voici un exemple d'utilisation:

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 commentaires

Hé merci pour votre réponse, malheureusement, je ne peux pas utiliser de crochets pour le moment. De plus, cela déclenchera toujours un nouveau rendu à chaque fois que nous écrivons dans l'entrée puisque nous éditons du texte au lieu de la valeur .


Ce n'est pas une solution à votre problème ... c'est un exemple du comportement de debounce ... C'est fait pour comparer les deux flux ..



0
votes

Alors ... Apparemment, il n'y a pas de solution. l'entrée prend la valeur de l'état. Alors que debounce empêche l'état de se déclencher.

J'ai fait une solution de contournement en utilisant ReactDOM.

this.refForm = React.createRef();
setFormDefaultValue(this.state, refForm)

puis l'utilisation:

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...`);
    });
  }
};

De cette façon, je peux remplir mon formulaire avec la valeur par défaut de l'état et continuer à utiliser anti-rebond.


0 commentaires

0
votes

Vous pouvez essayer ceci.

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}
    />
  );
});


0 commentaires