Триггерный рендеринг подкомпонента (реактивная таблица) с использованием хуков - PullRequest
0 голосов
/ 30 марта 2020

Я все еще новичок в React, функциональном программировании, Javascript и JSX, так что go просто, если это глупый вопрос.

Я изменяю один из примеров материала -ui таблицы из реактивной таблицы v7. Оригинальный код можно найти здесь . Пример полностью функциональный и использует React Hooks в отличие от классов, как и все компоненты шаблона, который я использую (кричите на creative-tim.com!)

Моя родительская функция (представитель страницы в моем приложении на панели мониторинга), например Users. js или Stations. js извлекает данные из API-интерфейса сервера в хуке useEffect. Эти данные затем передаются в качестве подпорки моему подкомпоненту ReactTables. js

По какой-то причине ReactTables. js не получает изменений в подпорку «данные» после завершения useEffect родительской страницы. Однако, как только я изменяю данные из подкомпонента ReactTables (в данном случае AddAlarmDialog. js), таблица перерисовывается и все мои данные внезапно появляются.

Как я могу вызвать перерисовку моего подкомпонента, когда данные возвращаются из useEffect родительского компонента? Я заметил, что в более старых версиях React была функция жизненного цикла под названием componentWillReceiveProps (). Это поведение, которое мне нужно подражать здесь?

Пример родительского компонента (аварийные сигналы. js):

import React, { useEffect, useState } from "react";
// @material-ui/core components

// components and whatnot
import GridContainer from "components/Grid/GridContainer.js";
import GridItem from "components/Grid/GridItem.js";

import ReactTables from "../../components/Table/ReactTables";

import { server } from "../../variables/sitevars.js";

export default function Alarms() {
  const [columns] = useState([
    {
      Header: "Alarm Name",
      accessor: "aName"
    },
    {
      Header: "Location",
      accessor: "aLocation"
    },
    {
      Header: "Time",
      accessor: "aTime"
    },
    {
      Header: "Acknowledged",
      accessor: "aAcked"
    },
    {
      Header: "Active",
      accessor: "aActive"
    }
  ]);

  const [data, setData] = useState([]);
  const [tableType] = useState("");
  const [tableLabel] = useState("Alarms");

  useEffect(() => {
    async function fetchData() {
      const url = `${server}/admin/alarms/data`;
      const response = await fetch(url);
      var parsedJSON = JSON.parse(await response.json());

      var tableElement = [];
      parsedJSON.events.forEach(function(alarm) {
        tableElement = [];
        parsedJSON.tags.forEach(function(tag) {
          if (alarm.TagID === tag.IDX) {
            tableElement.aName = tag.Name;
          }
        });
        tableElement.aTime = alarm.AlarmRcvdTime;
        parsedJSON.sites.forEach(function(site) {
          if (site.IDX === alarm.SiteID) {
            tableElement.aLocation = site.Name;
          }
        });
        if (alarm.Active) {
          tableElement.aActive = true;
        } else {
          tableElement.aActive = false;
        }
        if (!alarm.AckedBy && !alarm.AckedTime) {
          tableElement.aAcked = false;
        } else {
          tableElement.aAcked = true;
        }
        //const newData = data.concat([tableElement]);
        //setData(newData);
        data.push(tableElement);
      });
    }
    fetchData().then(function() {
      setData(data);
    });
  }, [data]);

  return (
    <div>
      <GridContainer>
        <GridItem xs={12} sm={12} md={12} lg={12}>
          <ReactTables
            data={data}
            columns={columns}
            tableType={tableType}
            tableLabel={tableLabel}
          ></ReactTables>
        </GridItem>
      </GridContainer>
    </div>
  );
}

Универсальный подкомпонент таблицы (ReactTables) . js):

import React, { useState } from "react";

// @material-ui/core components
import { makeStyles } from "@material-ui/core/styles";
// @material-ui/icons
import Assignment from "@material-ui/icons/Assignment";

// core components
import GridContainer from "components/Grid/GridContainer.js";
import GridItem from "components/Grid/GridItem.js";
import Card from "components/Card/Card.js";
import CardBody from "components/Card/CardBody.js";
import CardIcon from "components/Card/CardIcon.js";
import CardHeader from "components/Card/CardHeader.js";

import { cardTitle } from "assets/jss/material-dashboard-pro-react.js";
import PropTypes from "prop-types";
import EnhancedTable from "./subcomponents/EnhancedTable";

const styles = {
  cardIconTitle: {
    ...cardTitle,
    marginTop: "15px",
    marginBottom: "0px"
  }
};

const useStyles = makeStyles(styles);

export default function ReactTables(props) {
  const [data, setData] = useState(props.data);
  const [columns] = useState(props.columns);
  const [tableType] = useState(props.tableType);
  const [skipPageReset, setSkipPageReset] = useState(false)

  const updateMyData = (rowIndex, columnId, value) => {
    // We also turn on the flag to not reset the page
    setData(old =>
      old.map((row, index) => {
        if (index === rowIndex) {
          return {
            ...old[rowIndex],
            [columnId]: value
          };
        }
        return row;
      })
    );
  };

  const classes = useStyles();
  return (
    <GridContainer>
      <GridItem xs={12}>
        <Card>
          <CardHeader color="primary" icon>
            <CardIcon color="primary">
              <Assignment />
            </CardIcon>
            <h4 className={classes.cardIconTitle}>{props.tableLabel}</h4>
          </CardHeader>
          <CardBody>
            <EnhancedTable
              data={data}
              columns={columns}
              tableType={tableType}
              setData={setData}
              updateMyData={updateMyData}
              skipPageReset={skipPageReset}
              filterable
              defaultPageSize={10}
              showPaginationTop
              useGlobalFilter
              showPaginationBottom={false}
              className="-striped -highlight"
            />
          </CardBody>
        </Card>
      </GridItem>
    </GridContainer>
  );
}

ReactTables.propTypes = {
  columns: PropTypes.array.isRequired,
  data: PropTypes.array.isRequired,
  tableType: PropTypes.string.isRequired,
  tableLabel: PropTypes.string.isRequired,
  updateMyData: PropTypes.func,
  setData: PropTypes.func,
  skipPageReset: PropTypes.bool
};

** Для записи: если вы заметили лишний код в useEffect, это потому, что я возился и пытался выяснить, могу ли я вызвать повторную визуализацию .

1 Ответ

1 голос
/ 30 марта 2020

Я не знаю точно, как реагирующая таблица обрабатывает свой рендеринг, но если он является чисто функциональным компонентом, то реквизиты, которые вы передаете ему, должны измениться, прежде чем он будет переоценивать их. При проверке, изменились ли реквизиты, реагирование просто сделает простое сравнение ===, что означает, что если ваши реквизиты являются объектами, свойства которых изменяются, то он все равно будет оцениваться как тот же объект. Чтобы решить эту проблему, вы должны рассматривать все реквизиты как неизменные

. В вашем примере вы отправляете в массив data, а затем вызываете setData(data), что означает, что вы передаете один и тот же экземпляр массива. , Когда реагирует, сравнивает предыдущую версию данных с новой версией, которую вы устанавливаете в вызове на setDate, он будет думать, что data не изменился, потому что это та же ссылка.

Чтобы решить эту проблему, Вы можете просто создать новый массив из старого массива, расширив существующий массив до нового. Таким образом, вместо того, чтобы делать

data.push(tableElement);

Вы должны сделать

const newInstance = [...data, tableElement];

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

РЕДАКТИРОВАТЬ: Поэтому, посмотрев еще раз, я думаю, проблема в том, как вы используете параметр по умолчанию в хуке useState. Похоже, вы ожидаете, что для установки состояния от любых изменений реквизита, но в действительности этот параметр является просто значением по умолчанию, которое вы положите в компонент при его первом создании. Изменение реквизита входящих данных никоим образом не изменяет ваше состояние.

Если вы хотите обновить состояние в ответ на изменения в реквизите, вам нужно будет использовать ловушку useEffect и установить для рассматриваемого реквизита значение зависимость.

Но лично я бы постарался и не иметь, по сути, одинаковых данных, дублированных в состоянии в двух местах. Я думаю, что лучше всего было бы сохранить ваши данные в компоненте тревоги и добавить dataChanged обратный вызов или что-то, что примет ваш новый реквизит данных, и передать его обратно в состояние тревоги через параметр в обратном вызове

...