Привязать поле ввода к компоненту сетки

#javascript #reactjs #data-binding

#javascript #reactjs #привязка данных

Вопрос:

Как я могу синхронизировать значение SearchFilter с FilterGrid , чтобы я мог фильтровать данные (см. Код ниже)? У меня возникла проблема с очисткой поля от значения при вводе. Должно быть, проблема в том, как я использую перехватчики состояний.

Проблема, вероятно, связана с тем, как я вызываю onChange внутри handleOnChange функции, которая находится внутри SearchFilter . Я передал onChange «прослушиватель» в качестве свойства фильтра, чтобы я мог получить доступ к значению за пределами фильтра.

Не уверен, что это неправильный способ, потому что привязка значения directy к полю поиска в сетке выдала мне это предупреждение:

«Предупреждение: компонент изменяет контролируемый ввод на неконтролируемый. Вероятно, это вызвано изменением значения с определенного на неопределенное, чего не должно происходить. Выберите между использованием контролируемого или неконтролируемого элемента ввода в течение срока службы компонента. «


Вот ссылка на живую песочницу:

https://codesandbox.io/s/react-material-ui-search-d4073?file=/src/App.jsx

App.jsx

 import React, { useEffect, useState } from "react";
import FilterGrid from "./components/FilterGrid";
import jsonData from "./data/data.json";

const fetchData = () => Promise.resolve(jsonData);

const App = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData().then((json) => setData(json));
  }, []);

  const columns = [
    { key: "nameField", field: "name", title: "Name" },
    { key: "ageField", field: "age", title: "Age" }
  ];

  return <FilterGrid data={data} columns={columns} />;
};

export default App;
 

FilterGrid.jsx

 import React, { useState } from "react";
import SearchFilter from "./SearchFilter";
import MaterialTable from "material-table";

const FilterGrid = (props) => {
  const { data, columns } = props;
  const [filterValue, setFilterValue] = useState("");

  const handleChange = (value) => {
    setFilterValue(value);
    console.log("Value changed...");
  };

  const applyFilter = (records) =>
    records.filter((record) =>
      Object.values(record).some((value) => `${value}`.includes(filterValue))
    );

  const toolbar = () => (
    <div
      style={{
        display: "flex",
        flex: 1,
        flexDirection: "row",
        justifyContent: "flex-end"
      }}
    >
      <SearchFilter
        listeners={{
          onChange: handleChange
        }}
      />
    </div>
  );

  console.log("Changed filter:", filterValue);

  return (
    <div>
      <MaterialTable
        data={applyFilter(data)}
        columns={columns}
        components={{
          Toolbar: toolbar
        }}
        options={{
          search: false
        }}
      />
    </div>
  );
};

export default FilterGrid;

 

SearchFilter.jsx

 import { FormControl, Input } from "@material-ui/core";
import React, { useState } from "react";
import PropTypes from "prop-types";

const SearchFilter = (props) => {
  const {
    listeners: { onChange }
  } = props;

  const [internalValue, setInternalValue] = useState("");

  const handleOnChange = (e) => {
    setInternalValue(e.target.value);
    onChange(internalValue);
  };

  return (
    <FormControl>
      <Input
        type="search"
        placeholder="Search..."
        onChange={(e) => handleOnChange(e)}
        value={internalValue}
      />
    </FormControl>
  );
};

export default SearchFilter;
 

data.json

 [
  { "name": "Bill", "age": 21 },
  { "name": "Bob", "age": 31 },
  { "name": "Brad", "age": 41 }
]
 

package.json

 {
  "dependencies": {
    "@material-ui/core": "4.11.1",
    "material-table": "1.69.2",
    "prop-types": "15.7.2",
    "react": "17.0.0",
    "react-dom": "17.0.0"
  }
}
 

index.html

 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>React Material UI Search</title>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/icon?family=Material Icons"
    />
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
 

Ответ №1:

проблема заключается в асинхронном характере setState при выполнении:

   const handleOnChange = (e) => {
    setInternalValue(e.target.value);
    onChange(internalValue);
  };
 

internalValue по-прежнему остается пустым после onChange выполнения (оно не ожидает setInternalValue) для обновления состояния.

есть и другие вещи, которые следует учитывать. вы дублируете состояние, что не является хорошей практикой. вам лучше сохранить состояние фильтра FilterGrid и передать его в качестве реквизита.

еще одна вещь, которую я заметил, это то, что каждый тип ввода размывает поле ввода. Этого можно избежать, если объявить всплывающую подсказку снаружи и не передавать в качестве параметра компонента в FilterGrid

FilterGrid

 import React, { useState } from "react";
import SearchFilter from "./SearchFilter";
import MaterialTable from "material-table";

const Toolbar = ({ filterValue, setFilterValue }) => (
  <div
    style={{
      display: "flex",
      flex: 1,
      flexDirection: "row",
      justifyContent: "flex-end"
    }}
  >
    <SearchFilter filterValue={filterValue} setFilter={setFilterValue} />
  </div>
);

const FilterGrid = (props) => {
  const { data, columns } = props;
  const [filterValue, setFilterValue] = useState("");

  const applyFilter = (records) =>
    records.filter((record) =>
      Object.values(record).some((value) => `${value}`.includes(filterValue))
    );

  console.log("Changed filter:", filterValue);

  return (
    <div>
      <Toolbar filterValue={filterValue} setFilterValue={setFilterValue} />
      <MaterialTable
        data={applyFilter(data)}
        columns={columns}
        options={{
          search: false
        }}
      />
    </div>
  );
};

export default FilterGrid;
 

Фильтр поиска

 import { FormControl, Input } from "@material-ui/core";
import React from "react";
import PropTypes from "prop-types";

const SearchFilter = (props) => {
  const { filterValue, setFilter } = props;

  const handleOnChange = (e) => {
    setFilter(e.target.value);
  };

  return (
    <FormControl>
      <Input
        type="search"
        placeholder="Search..."
        onChange={handleOnChange}
        value={filterValue}
      />
    </FormControl>
  );
};

SearchFilter.propTypes = {
  filterValue: PropTypes.string.isRequired,
  setFilter: PropTypes.func.isRequired
};

export default SearchFilter;
 

Комментарии:

1. Таким образом, нет способа настроить панель инструментов для сетки с помощью components.Toolbar = () => (<Toolbar ... />) ?

2. Я не в курсе. Желаемый вами подход приводит к повторному отображению на панели инструментов, что приводит к размытию в SeachField для каждого типа пользователя.