Синхронизация переключателей пользовательского интерфейса Material со столбцами MaterialTable

#javascript #reactjs #data-binding

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

Вопрос:

Я не могу повторно отобразить свою сетку после изменения hidden состояния столбца. Я думал, что изменение columns приведет к повторному отображению сетки, поскольку это связанный атрибут.

Вот интерактивный пример, размещенный на codesandbox.io .

index.jsx

 import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
 

App.js

 import React from "react";
import SuperTable from "./components/SuperTable";

const data = [
  { id: 1, type: "Developer", name: "Bob", age: 21 },
  { id: 2, type: "Developer", name: "Mary", age: 28 },
  { id: 3, type: "Manager", name: "Steve", age: 42 }
];

const columns = [
  {
    field: "id",
    title: "ID",
    searchable: true,
    hidden: true
  },
  {
    field: "name",
    title: "Name",
    searchable: true
  },
  {
    field: "age",
    title: "Age",
    searchable: true
  },
  {
    field: "type",
    title: "Type",
    searchable: true
  }
];

const App = () => {
  return <SuperTable data={data} columns={columns} />;
};

export default App;
 

SuperTable.jsx

 import MaterialTable from "material-table";
import React, { forwardRef } from "react";
import { makeStyles } from "@material-ui/core/styles";
import ArrowDownward from "@material-ui/icons/ArrowDownward";
import ChevronLeft from "@material-ui/icons/ChevronLeft";
import ChevronRight from "@material-ui/icons/ChevronRight";
import ClearIcon from "@material-ui/icons/Clear";
import FirstPage from "@material-ui/icons/FirstPage";
import LastPage from "@material-ui/icons/LastPage";
import Search from "@material-ui/icons/Search";
import ColumnToggler from "./ColumnToggler";

const useStyles = makeStyles((theme) => ({
  wrapper: {
    display: "flex",
    flexDirection: "row"
  },
  widget: {
    display: "flex",
    flexDirection: "column",
    flex: 0.5
  }
}));

const tableIcons = {
  ResetSearch: forwardRef((props, ref) => <ClearIcon {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => (
    <ChevronLeft {...props} ref={ref} />
  )),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />)
};

const SuperTable = (props) => {
  const { data, columns, ...rest } = props;

  const classes = useStyles();

  const onToggle = (columnField, show) => {
    const foundColumn = columns.find((col) => col.field === columnField);
    if (foundColumn) {
      foundColumn.hidden = show;
    }
  };

  return (
    <div className={classes.wrapper}>
      <MaterialTable
        style={{
          position: "unset",
          display: "flex",
          flexDirection: "column",
          flex: 1
        }}
        title={null}
        data={data}
        columns={columns}
        icons={tableIcons}
        pageSize={5}
        {...rest}
      />
      <div className={classes.widget}>
        <ColumnToggler onChange={onToggle} columns={columns} />
      </div>
    </div>
  );
};

SuperTable.propTypes = {
  ...MaterialTable.propTypes
};

export default SuperTable;
 

ColumnToggler.jsx

 import React from "react";
import { Typography } from "@material-ui/core";
import PropTypes from "prop-types";
import { Container, makeStyles } from "@material-ui/core";
import ColumnToggleSwitch from "./ColumnToggleSwitch";

const useStyles = makeStyles((theme) => ({
  formControl: {
    margin: theme.spacing(1),
    minWidth: 200
  }
}));

const ColumnToggler = (props) => {
  const { columns, onChange } = props;

  const classes = useStyles();

  const toggleChecked = (field, show) => {
    onChange(field, show);
  };

  console.log("Updated toggler...");

  return (
    <Container className={classes.formControl}>
      <Typography>Toggle Columns</Typography>
      {columns.map((column) => (
        <ColumnToggleSwitch column={column} onChange={toggleChecked} />
      ))}
    </Container>
  );
};

ColumnToggler.propTypes = {
  onChange: PropTypes.func.isRequired
};

export default ColumnToggler;
 

Columntoggles Switch.jsx

 import React, { useState } from "react";
import PropTypes from "prop-types";
import { FormControlLabel, FormGroup, Switch } from "@material-ui/core";

const ColumnToggleSwitch = (props) => {
  const {
    column: { field, hidden, title },
    onChange
  } = props;
  const [checked, setChecked] = useState(hidden == null || !hidden);

  const handleChange = (event, newValue) => {
    setChecked(newValue);
    onChange(event.currentTarget.name, newValue);
  };

  return (
    <FormGroup>
      <FormControlLabel
        control={
          <Switch
            key={field}
            name={field}
            checked={checked}
            onChange={handleChange}
            color="primary"
            inputProps={{ "aria-label": "primary checkbox" }}
            size="small"
          />
        }
        label={title || field}
      />
    </FormGroup>
  );
};

ColumnToggleSwitch.propTypes = {
  column: PropTypes.object.isRequired,
  onChange: PropTypes.func.isRequired
};

export default ColumnToggleSwitch;
 

package.json

 {
  "dependencies": {
    "@material-ui/core": "4.11.2",
    "@material-ui/icons": "4.11.2",
    "material-table": "1.69.2",
    "react": "17.0.0",
    "react-dom": "17.0.0",
    "react-scripts": "3.4.3"
  }
}
 

Ответ №1:

Основная проблема заключается в том, что реквизиты React неизменяемы. Если вы измените prop в своем компоненте, значение не будет гарантировать повторный рендеринг.

Вы изменяете свой columns prop в своем SuperTable компоненте, и это не может работать должным образом.

Решение состоит в том, чтобы определить столбцы как константу, импортировать ее и установить в качестве начального состояния вашего columns состояния. columns в любом случае это константа, и ее не нужно отправлять как реквизит.

components/columns.js :

 // define columns and export it
 

SuperTable.jsx :

 import initialColumns from './columns';

...
const [columns, setColumns] = useState(initialColumns);
 

Состояния также неизменяемы, поэтому вам не следует делать:

 foundColumn.hidden = show;
 

но вместо этого следует сделать:

 const newColumns = ....;
setColumns(newColumns);
 

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

1. Я заметил, что если я пытаюсь отобразить скрытый столбец, в консоли выдается ошибка. Я понял, что данные столбца для скрытого столбца не имеют назначенной расчетной ширины. Я просто создал свои собственные ширины, и это работает. Это лучший способ сделать это? Смотрите мой обновленный (рабочий) пример. Кроме того, столбцы в конечном итоге будут динамическими, поэтому константа не будет работать … Переключение столбцов таблицы Material UI 2.0