Отказ внутри функционального компонента в React.js

Попытка создать задержку для компонента реакции, который имеет поле ввода, которое обновляется при изменении.

Вот код:

import React from 'react'; 
import Input from "@material-ui/core/Input";
import {debounce} from "lodash";
...

const SampleRow = () => {
    let [newFriend, setNewFriend] = React.useState({name: '', lastSeen: '', contactIn: ''});

    const onAddFriend = debounce(() => {
        if(newFriend.name.length === 0){
            return;
        }
        console.log(newFriend.name);
    }, 2000);

    return (
        <TableRow>
            <TableCell component="th" scope="row">
                <Input
                    value={newFriend.name}
                    onChange={(event) => {
                        setNewFriend({...newFriend, name: event.target.value});
                        onAddFriend();
                    }}
                    placeholder={"Add friend"}
                    disableUnderline={true}
                />
            </TableCell>
            <TableCell align="left">{newFriend.lastSeen ? newFriend.lastSeen : ''}</TableCell>
            <TableCell align="left">{newFriend.contactIn ? newFriend.contactIn : ''}</TableCell>
            <TableCell align="left" className={classes.externalLinks}/>
        </TableRow>
    )
};

Что происходит, так это то, что console.log распечатывает каждое изменение (после тайм-аута в 2 секунды) и не отменяет предыдущие триггеры.

Вопрос почему так происходит? и как это можно было решить?

Я нашел похожие вопросы со сложной логикой useDebounce или классовыми компонентами. Не нашел ссылку на эту проблему.


person YanivGK    schedule 16.07.2020    source источник
comment
вам придется очистить уже вызванную функцию debounce.   -  person Sheelpriy    schedule 16.07.2020
comment
Разве это не вся концепция debounce? вызов функции должен сбросить time-interval.   -  person YanivGK    schedule 16.07.2020
comment
Я думаю, что рендеринг функции может вызвать проблему. Я пытался event.persist(), но это было не так.   -  person YanivGK    schedule 16.07.2020
comment
Именно поэтому решения, которые вы нашли, сложны. С хуками вам нужно использовать useRef для хранения/обработки/ссылки на обратный вызов. Вот как к этому подходят существующие хуки, такие как useDebouncedCallback. Существует отличная статья о useRef с такой функциональностью, как useInterval.   -  person Alexander Staroselsky    schedule 16.07.2020


Ответы (3)


Проблема здесь в том, что ваша функция debounce воссоздается при каждом рендеринге.

Один из способов решить эту проблему — убрать его из функционального компонента. Другой способ - изменить ваш компонент на компонент класса и сохранить вашу отмененную функцию как свойство.

const { useState } = React
const { debounce } = _;

const onAddFriend = debounce(console.log, 2000);

const App = () => {
 
  const [newFriend, setNewFriend] = useState({name: '',   lastSeen: '', contactIn: ''});
  
  const handleInput = (event) => {
    setNewFriend({...newFriend, name: event.target.value});
    onAddFriend(event.target.value);
  }
  
  return (
    <div className="box">
      <form className="form">
        <div class="field">
          <label for="name-1">Update  {newFriend.name}</label>
          <div class="control">
            <input type="text" name="name-1" value={newFriend.name}
                    onChange={handleInput} class="input"/>
          </div>
        </div>
      </form>
    </div>
  )
}

ReactDOM.render(<App />,
document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

<div id="root"></div>

person zlace    schedule 16.07.2020
comment
Позже я подумал, что воссоздание функции может вызвать проблему, но не подумал об экспорте ее как внешней функции для решения - красиво, просто и работает, как ожидалось - спасибо! - person YanivGK; 17.07.2020

Чао, попробуй отменить предыдущий триггер:

import React from 'react'; 
import Input from "@material-ui/core/Input";
import {debounce} from "lodash";
...

const SampleRow = () => {
  let [newFriend, setNewFriend] = React.useState({name: '', lastSeen: '', contactIn: 
''});

const onAddFriend = debounce((abort) => {
    if(abort){
        return;
    }
    console.log(newFriend.name);
}, 2000);

return (
    <TableRow>
        <TableCell component="th" scope="row">
            <Input
                value={newFriend.name}
                onChange={(event) => {
                    setNewFriend({...newFriend, name: event.target.value});
                    let abort = false;
                    if(event.target.value.length === 0){
                      abort = true;
                    }
                    onAddFriend(abort);
                }}
                placeholder={"Add friend"}
                disableUnderline={true}
            />
        </TableCell>
        <TableCell align="left">{newFriend.lastSeen ? newFriend.lastSeen : ''}</TableCell>
        <TableCell align="left">{newFriend.contactIn ? newFriend.contactIn : ''}</TableCell>
        <TableCell align="left" className={classes.externalLinks}/>
    </TableRow>
)
  };
person Giovanni Esposito    schedule 16.07.2020
comment
Это не работает. Последний триггер отменяется, но остальные по-прежнему активны (и выводятся на консоль по истечении заданного времени). - person YanivGK; 16.07.2020
comment
Хорошо, другое решение, которое я знаю, это решение с флагом. Я обновлю свой ответ через некоторое время. - person Giovanni Esposito; 16.07.2020
comment
К сожалению, это не работает. Я понимаю идею этого, но я предполагаю, что повторный рендеринг функции onAddFriend приводит к тому, что она пропускает флаг. - person YanivGK; 16.07.2020

Попробуй это. Он всегда будет проверять существующие debounce в очереди и отменять их. См.: Как отменить отклоненную функцию после ее вызова и до ее выполнения?

person Sheelpriy    schedule 16.07.2020