У меня есть функциональный компонент, построенный на основе компонента Реагировать Таблица, который использует Клиент Apollo GraphQL для разбиения на страницы и поиска на стороне сервера. Я пытаюсь реализовать отмену дребезга для поиска, чтобы к серверу выполнялся только один запрос, как только пользователь перестанет вводить это значение. Я пробовал решения Лодаш и потрясающее обещание отказа, но все же запрос к серверу выполняется для каждого символа, введенного в поле поиска.
Вот мой компонент (с отредактированной неактуальной информацией):
import React, {useEffect, useState} from 'react';
import ReactTable from "react-table";
import _ from 'lodash';
import classnames from 'classnames';
import "react-table/react-table.css";
import PaginationComponent from "./PaginationComponent";
import LoadingComponent from "./LoadingComponent";
import {Button, Icon} from "../../elements";
import PropTypes from 'prop-types';
import Card from "../card/Card";
import './data-table.css';
import debounce from 'lodash/debounce';
function DataTable(props) {
const [searchText, setSearchText] = useState('');
const [showSearchBar, setShowSearchBar] = useState(false);
const handleFilterChange = (e) => {
let searchText = e.target.value;
setSearchText(searchText);
if (searchText) {
debounceLoadData({
columns: searchableColumns,
value: searchText
});
}
};
const loadData = (filter) => {
// grab one extra record to see if we need a 'next' button
const limit = pageSize + 1;
const offset = pageSize * page;
if (props.loadData) {
props.loadData({
variables: {
hideLoader: true,
opts: {
offset,
limit,
orderBy,
filter,
includeCnt: props.totalCnt > 0
}
},
updateQuery: (prev, {fetchMoreResult}) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
[props.propName]: [...fetchMoreResult[props.propName]]
});
}
}).catch(function (error) {
console.error(error);
})
}
};
const debounceLoadData = debounce((filter) => {
loadData(filter);
}, 1000);
return (
<div>
<Card style = {{
border: props.noCardBorder ? 'none' : ''
}}>
{showSearchBar ? (
<span className = "card-header-icon"><Icon className='magnify'/></span>
<input
autoFocus = {true}
type = "text"
className = "form-control"
onChange = {handleFilterChange}
value = {searchText}
/>
<a href = "javascript:void(0)"><Icon className='close' clickable
onClick = {() => {
setShowSearchBar(false);
setSearchText('');
}}/></a>
) : (
<div>
{visibleData.length > 0 && (
<li className = "icon-action"><a
href = "javascript:void(0)"><Icon className='magnify' onClick= {() => {
setShowSearchBar(true);
setSearchText('');
}}/></a>
</li>
)}
</div>
)
)}
<Card.Body className='flush'>
<ReactTable
columns = {columns}
data = {visibleData}
/>
</Card.Body>
</Card>
</div>
);
}
export default DataTable
... и вот результат: связь
debounceLoadData
будет новая функция для каждого рендера. Вы можете использовать хук useCallback
, чтобы убедиться, что одна и та же функция сохраняется между рендерингами, и она будет работать должным образом.
useCallback(debounce(loadData, 1000), []);
const { useState, useCallback } = React;
const { debounce } = _;
function App() {
const [filter, setFilter] = useState("");
const debounceLoadData = useCallback(debounce(console.info, 1000), []);
function handleFilterChange(event) {
const { value } = event.target;
setFilter(value);
debounceLoadData(value);
}
return <input value = {filter} onChange = {handleFilterChange} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src = "https://unpkg.com/react@16/umd/react.development.js"></script>
<script src = "https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id = "root"></div>
@ user2602152 Я тоже получаю это предупреждение. Документация по хукам React предупреждает, что мемоизированные функции не гарантируют, что они не будут выгружены и повторно выполнены в будущем для освобождения памяти. Не уверен насчет хука useCallback.
Чтобы добавить к ответу Толле: если вы хотите в полной мере использовать хуки, вы можете использовать хук useEffect
, чтобы следить за изменениями в фильтре и запускать функцию debouncedLoadData
, когда это происходит:
const { useState, useCallback, useEffect } = React;
const { debounce } = _;
function App() {
const [filter, setFilter] = useState("");
const debounceLoadData = useCallback(debounce(fetchData, 1000), []);
useEffect(() => {
debounceLoadData(filter);
}, [filter]);
function fetchData(filter) {
console.info(filter);
}
return <input value = {filter} onChange = {event => setFilter(event.target.value)} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
не зафиксирует ли он начальное значение filter
внутри функции и никогда не обновит его?
@PavelLuzetskiy К сожалению, я сделал небольшую ошибку в своем решении, что позволяет мне понять, почему вы так думаете. Я отредактировал его сейчас, но сначала я делал debounce(console.info(filter), 1000)
, что не сработало, потому что передача console.info(filter)
запустит его немедленно, а не вызовет. Передача filter => console.info(filter)
в вызов debounce
работает, и функция получит новое значение filter
из вызова debounceLoadData(filter)
в useEffect
. В качестве альтернативы, передача функции без ее вызова (как в моем редактировании) также передает ей параметры.
это было полезно, спасибо, я использовал гораздо более сложную работу, чтобы добиться того же
Я думаю, что вам не нужно useEffect
в этом случае, просто вызовите функцию debounced в обработчике onChange: return <input value = {filter} onChange = {handleChangeEvent} />;
function handleChangeEvent(event) { setFilter(event.target.value); debounceLoadData(event.target.value) }
Вы должны помнить об отключенной функции между рендерами.
Тем не менее, вы должны нет использовать useCallback
, чтобы запомнить отклоненную (или дросселированную) функцию, как это предлагается в других ответах. useCallback
предназначен для встроенных функций!
Вместо этого используйте useMemo
, чтобы запомнить функцию debounce между рендерами:
useMemo(() => debounce(loadData, 1000), []);
Это также средство от ошибки React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead react-hooks/exhaustive-deps
во времени CI.
Относительно 'useMemo'... reactjs.org/docs/hooks-reference.html#usememo Вы можете полагаться на useMemo как на оптимизацию производительности, а не как на семантическую гарантию. В будущем React может решить «забыть» некоторые ранее запомненные значения и пересчитать их при следующем рендеринге, например. освободить память для закадровых компонентов. Напишите свой код так, чтобы он по-прежнему работал без использования useMemo, а затем добавьте его для оптимизации производительности.
При использовании этого шаблона я получаю предупреждение от eslint: «React Hook useCallback получил функцию, зависимости которой неизвестны. Вместо этого передайте встроенную функцию. eslintreact-hooks/exhaustive-deps». Любая идея, как я могу избежать этой ошибки? Я попытался обернуть debounce встроенной функцией, но затем debounce не смог выполнить свою работу.