Как создать динамическую таблицу в React JS в зависимости от ключей?

#javascript #reactjs

#javascript #reactjs

Вопрос:

Я пытаюсь создать компонент динамической таблицы в React JS. В настоящее время компонент имеет только статический заголовок, включающий наиболее распространенные ключи результатов. Некоторые результаты также содержат дополнительную информацию, например, номер телефона, степень. Как мне динамически расширять таблицу дополнительным столбцом в зависимости от представления ключа / значения? Должен ли я работать с state и делать видимым, когда присутствует, или я должен предварительно обработать таблицу (но как мне обеспечить правильный порядок)? Есть ли лучшие способы сделать это?

 import React, { Component } from 'react';

class ResultTable extends Component {

  addTableRow = (result) => {
    return (
      <tr key={result._id}>
        <td>{result.lastname}</td>
        <td>{result.firstname}</td>
        <td>{result.city}</td>
        <td>{result.zip}</td>
        <td>{result.street} {result.street_number}</td>
      </tr>)
  }

  createTable = (results) => {
    return (
      <table className="striped highlight ">
        <thead>
          <tr>
            <th>Lastname</th>
            <th>Firstname</th>
            <th>City</th>
            <th>ZIP</th>
            <th>Street</th>
          </tr>
        </thead>
        <tbody>
          {results.map((result, index) => {
            return this.addTableRow(result)
          })}
        </tbody>
      </table>
    )
  }

  render() {
    return (
      <div>
        {this.props.results.length ? (
          <div className="card">
            {this.createTable(this.props.results)}
          </div>
        ) : null}
      </div>
    )
  }
}

export default ResultTable
 

results посмотри вот так:

 [
    { "_id": 1, "area_code": "555", "city": "Berlin", "firstname": "John", "lastname": "Doe", "phonenumber": "12345678", "zip": "12345"},
    { "_id": 2, "area_code": "555", "city": "Frankfurt", "firstname": "Arnold", "lastname": "Schwarzenegger", "phonenumber": "12121212", "street": "Main Street", "street_number": "99", "zip": "545454"},
    { "_id": 3, "area_code": "123", "firstname": "Jane", "lastname": "Doe", "phonenumber": "777777", "fav_color": "blue"},
    { "_id": 4, "area_code": "456", "firstname": "Scooby", "lastname": "Doo",  "phonenumber": "444444"}, "note": "Lazy but cool"
]
 

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

1. Я следую этому, чтобы увидеть решения 🙂 Но вы могли бы сопоставить свои результаты, проверить наличие других заголовков столбцов, которые не являются заголовками по умолчанию, а затем соответствующим образом добавить их в свой заголовок. Единственное, что тогда было бы упорядочено, как вы сказали.

2. Вы имеете в виду, что у вас может быть столбец с phone_number больше, и некоторые строки не будут иметь в нем значения, но некоторые да? и вы хотите динамически добавлять столбец, если столбцы не существуют?

3. @DarioRega Да, это то, к чему я стремлюсь. Сначала общие ключи, и если они существуют, добавьте больше столбцов.

4. Хорошо, я делаю codesandbox прямо сейчас

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

Ответ №1:

Хорошо, хорошо, мне потребовалось около двух часов, чтобы сделать это и вернуться к react, прошло много времени, я к нему не прикасался. Так что, если вы все видите что-то для рефакторинга, с удовольствием можете редактировать!

Сначала код :

 import React from "react";
import "./styles.css";

export default class App extends React.Component {
  state = {
    columns: [],
    columnsToHide: ["_id"],
    results: [
      {
        _id: 1,
        firstname: "Robert",
        lastname: "Redfort",
        city: "New York",
        zip: 1233,
        street: "Mahn Street",
        street_number: "24A",
        favoriteKebab: "cow"
      },
      {
        _id: 2,
        firstname: "Patty",
        lastname: "Koulou",
        city: "Los Angeles",
        zip: 5654,
        street: "Av 5th Central",
        street_number: 12
      },
      {
        _id: 3,
        firstname: "Matt",
        lastname: "Michiolo",
        city: "Chicago",
        zip: 43452,
        street: "Saint Usk St",
        street_number: 65,
        phoneNumber: "0321454545"
      },
      {
        _id: 4,
        firstname: "Sonia",
        lastname: "Remontada",
        city: "Buenos Aires",
        zip: "43N95D",
        street: "Viva la Revolution Paso",
        street_number: 5446,
        country: "Argentina"
      }
    ]
  };
  componentDidMount() {
    this.mappDynamicColumns();
  }

  mappDynamicColumns = () => {
    let columns = [];
    this.state.results.forEach((result) => {
      Object.keys(result).forEach((col) => {
        if (!columns.includes(col)) {
          columns.push(col);
        }
      });
      this.setState({ columns });
    });
  };

  addTableRow = (result) => {
    let row = [];
    this.state.columns.forEach((col) => {
      if (!this.state.columnsToHide.includes(col)) {
        row.push(
          Object.keys(result).map((item) => {
            if (result[item] amp;amp; item === col) {
              return result[item];
            } else if (item === col) {
              return "No Value";
            }
          })
        );
        row = this.filterDeepUndefinedValues(row);
      }
    });

    return row.map((item, index) => {
      // console.log(item, "item ?");
      return (
        <td
          key={`${item}--${index}`}
          className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
        >
          {item}
        </td>
      );
    });
  };

  mapTableColumns = () => {
    return this.state.columns.map((col) => {
      if (!this.state.columnsToHide.includes(col)) {
        const overridedColumnName = this.overrideColumnName(col);
        return (
          <th
            key={col}
            scope="col"
            className="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
          >
            {overridedColumnName}
          </th>
        );
      }
    });
  };

  filterDeepUndefinedValues = (arr) => {
    return arr
      .map((val) =>
        val.map((deepVal) => deepVal).filter((deeperVal) => deeperVal)
      )
      .map((val) => {
        if (val.length < 1) {
          val = ["-"];
          return val;
        }
        return val;
      });
  };

  // if you want to change the text of the col you could do here in the .map() with another function that handle the display text

  overrideColumnName = (colName) => {
    switch (colName) {
      case "phoneNumber":
        return "Phone number";
      case "lastname":
        return "Custom Last Name";
      default:
        return colName;
    }
  };

  createTable = (results) => {
    return (
      <table class="min-w-full divide-y divide-gray-200">
        <thead>
          <tr>{this.mapTableColumns()}</tr>
        </thead>
        <tbody>
          {results.map((result, index) => {
            return <tr key={result._id}>{this.addTableRow(result)}</tr>;
          })}
        </tbody>
      </table>
    );
  };

  render() {
    return (
      <div class="flex flex-col">
        <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
          <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
            <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
              {this.state.results.length ? (
                <div className="card">
                  {this.createTable(this.state.results)}
                </div>
              ) : null}
            </div>
          </div>
        </div>
      </div>
    );
  }
}

 

Почему не определено? Ну, как я уже сказал, я немного прогнил в react (и, возможно, в codding ;)), Но я не нашел другого способа сохранить порядок каждого значения в принадлежащем ей столбце, чтобы оно не было в ее текущем индексе в массиве.

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

полезная нагрузка строки перед отправкой обратно в html такова :

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

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

После каждого нажатия мне нужно очистить мои глубоко вложенные значения в массивах, поэтому я придумал функцию filterDeepUndefinedValues
row = this.filterDeepUndefinedValues(row)

И сама функция

 filterDeepUndefinedValues = (arr) => {
  return arr
    .map((val) => val.map((deepVal) => deepVal).filter((deeperVal) => deeperVal))
    .map((val) => {
      if (val.length < 1) {
        val = ["-"];
        return val;
      }
      return val;
    });
};

 

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

Как массив принимается в функции изначально введите описание изображения здесь

Во второй части я просто заменяю пустое содержимое массива дефисом в массиве

Первая обработка с помощью .map и filter() введите описание изображения здесь

Замените пустой массив дефисом
введите описание изображения здесь

Возврат addTableRow

 return row.map((item, index) => {
  return (
    <td
      key={`${item}--${index}`}
      className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
    >
      {item}
    </td>
  );
});
 

Здесь мы просто сопоставляем все значения из строк и отображаем td для каждого массива строк

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

Ссылка на Codesandbox: https://codesandbox.io/s/bold-mendel-vmfsk?file=/src/App.js

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

1. Хорошая работа! S за усилия

2. Спасибо! Я увлекся проблемой, ха-ха

3. @DarioRega Спасибо за ваши усилия. Я добавил массив результатов в свой вступительный пост. Правильно ли я понимаю, что если я предварительно заполню состояние столбцов некоторыми значениями, я смогу повлиять на порядок заголовка таблицы? Я также считаю, что если свойство не существует, оно будет проигнорировано, по крайней мере, в настоящее время оно работает именно так. Постараюсь и обновлю. Еще раз спасибо.

Ответ №2:

Основываясь на ответе @DarioRega, я опубликую свое решение ниже. Ключ к моему решению — это его mappDynamicColumns функция. В этом решении headers состояние предварительно заполняется теми заголовками, которые я хочу иметь в первую очередь при рендеринге таблицы. fetchTableHeaders() перебирает все результаты и добавляет заголовки по мере их появления в результате. Также можно игнорировать определенные ключи результатов, если они не нужны в качестве столбцов (заслуги @DarioRega).

 import React, { Component } from 'react';

class ResultTable extends Component {

  state = {
    headers: ['lastname', 'firstname', 'city', 'zip', 'street'],
    ignore_headers: ['_id', 'flags', 'street_number'],
  }

  addTableRow = (headers, result) => {
    return (
      <tr key={result._id}>
        {headers.map((h) => {
          if(this.state.ignore_headers.includes(h)) return null
          if(h === 'street') return (<td>{result.street} {result.street_number}</td>)
          return (<td>{result[h]}</td>)
        })}
      </tr>)
  }

  createTable = (results) => {

    let headers = this.fetchTableHeaders(results);

    return (
      <table className="striped highlight ">
        <thead>
          <tr>
            {headers.map((h) => {
              return (<th>{this.makeHeaderStr(h)}</th>)
            })}
          </tr>
        </thead>
        <tbody>
          {results.map((result) => {
            return this.addTableRow(headers, result)
          })}
        </tbody>
      </table>
    )
  }

  fetchTableHeaders = (results) => {
    let headers = [...this.state.headers];
    results.forEach((result) => {
      Object.keys(result).forEach((r) => {
        if(!this.state.ignore_headers.includes(r) amp;amp; !headers.includes(r)) {
          headers.push(r);
        }
      });
    });
    return headers
  }

  makeHeaderStr = (header_str) => {
    if(typeof header_str !== 'string') return header_str
    return (header_str.charAt(0).toUpperCase()   header_str.slice(1)).replace('_', ' ')
  }

  render() {
    return (
      <div>
        {this.props.results.length ? (
          <div className="card">
            {this.createTable(this.props.results)}
          </div>
        ) : null}
      </div>
    )
  }
}

export default ResultTable