Home > Net >  Why are tables so hard in React Native?
Why are tables so hard in React Native?

Time:12-27

I'm trying to recreate html table functionality in React Native and I just cannot figure this out.

I have 1 column for item name (short), and another for item description (potentially very long).

I want the first column to take up only as much space as it needs and the second column to flex and then text-wrap when it runs out of room. That part's easy, but then the items in the first column all have different widths. And if I make the columns first instead in order to solve that problem, then getting the corresponding items in the first column to flex vertically to stay aligned is tripping me up. HTML tables do this effortlessly. Why is it so hard in Native?

Is there really no way to do a truly flexible table in this language?

I've tried different variations on flex, but I don't want it to be a fixed width or ratio for either column, since I want to leave the option for font sizes later, which would break it.

react-native-paper fails because DataTable.Cell doesn't allow for multiline, and adding in the functionality messes with the alignment, bringing me right back to where I started.

EDIT: In html, I would do it like this:

<html>
    <head>
        <style>
            td:first-child {white-space: nowrap; }
        </style>
    </head>
    <body>
        <table>
            <tr>
                <td>
                    I am some data!
                </td>
                <td>
                    Two households, both alike in dignity, in fair Verona, where we lay our scene, from ancient grudge break to new mutiny, where civil blood makes civil hands unclean.
                </td>
            </tr>
            <tr>
                <td>
                    I'm data too!
                </td>
                <td>
                    From forth the fatal loins of these two foes, a pair of star-cross'd lovers take their life; whose misadventured piteous overthrows do with their death bury their parents' strife.
                </td>
            </tr>
            <tr>
                <td>
                    I am also some data!
                </td>
                <td>
                    The fearful passage of their death-mark'd love, and the continuance of their parents' rage, which, but their children's end, nought could remove, is now the two hours' traffic of our stage; the which if you with patient ears attend, what here shall miss, our toil shall strive to mend.
                </td>
            </tr>
        </table>
    </body>
</html>

resulting in:

enter image description here

CodePudding user response:

You could create a component called Table which represents the table itself and one component called TableRow which represents one row of the table.

import React from 'react';
import { View } from 'react-native';
import TableRow from './TableRow';

const Table = () => (
  <View style={{ flexDirection: 'column' }}>
    <TableRow itemName="Item 1" itemDescription="Description for item 1" />
    <TableRow itemName="Item 2" itemDescription="Description for item 2" />
    <TableRow itemName="Item 3" itemDescription="Description for item 3" />
  </View>
);

export default Table;

and

import React from 'react';
import { View, Text } from 'react-native';

const TableRow = ({ itemName, itemDescription }) => (
  <View style={{ flexDirection: 'row' }}>
    <View style={{ width: 'auto', alignItems: 'flex-start' }}>
      <Text>{itemName}</Text>
    </View>
    <View style={{ flex: 3, alignItems: 'flex-start' }}>
      <Text>{itemDescription}</Text>
    </View>
  </View>
);

export default TableRow;

in my opinion using flex: 1 instead of width: 'auto' looks better but of course i don't know what your prerequisites are.

To fill the Table with data you need pass the table component an array of items. To do this modify the Table component

import React from 'react';
import { View } from 'react-native';
import TableRow from './TableRow';

const Table = ({ items }) => (
  <View style={{ flexDirection: 'column' }}>
    {items.map(item => (
      <TableRow
        key={item.name}
        itemName={item.name}
        itemDescription={item.description}
      />
    ))}
  </View>
);

export default Table;

Now you can do the following:

import React from 'react';
import Table from './Table';

const items = [
  { name: 'Item 1', description: 'Description for item 1' },
  { name: 'Item 2', description: 'Description for item 2' },
  { name: 'Item 3', description: 'Description for item 3' },
];

const App = () => (
  <Table items={items} />
);

export default App;

This will fill your table with data.

CodePudding user response:

Okay, I think I got pretty much the functionality I wanted. There's probably a way more extensible way to do this, and this is probably wildly inefficient, but it seems to work.

The gist is that I can use a state variable to store the desired width of each cell, and then in onLayout, I can call setColWidth to update that variable whenever the layout changes. Then, I can just use style to set the minimum width to the width of the biggest cell.

Then there's an array that determines whether a given column gets shrunk to allow room for others.

Finally, I can call alignItems in the parent View to shrink-wrap the tables to the minimum size that fits the data

import React, {useState} from 'react';
import {LayoutChangeEvent, useWindowDimensions, View} from 'react-native';

const Table = ({
  data,
  rowStyle = undefined,
  priviledge = new Array(data.length).fill(false),
}: {
  data: any[];
  rowStyle: Object | undefined;
  priviledge: boolean[];
}) => {
  // Initialize list of widths
  const [colWidth, setColWidth] = useState<number[][]>(
    data.map(row => new Array(Object.keys(row).length).fill(0)),
  );

  // Get widows size
  const maxSize = useWindowDimensions();

  if (!colWidth || !maxSize) {
    return <></>;
  }

  // Fix issues of going off screen
  const onLayout = (event: LayoutChangeEvent, row: number, col: number) => {
    // Get current width
    var {width, __} = event.nativeEvent.layout;

    // Find total row width
    const sum =
      colWidth[row].reduce((partialSum, a) => partialSum   a, 0) -
      colWidth[row][col]  
      width;

    // Shrink unpriviledged components
    if (!priviledge[col] && sum > maxSize.width) {
      width = width - (sum - maxSize.width);
      if (width < 0) {
        width = 0;
      }
    }

    // Store width in colWidth array
    colWidth[row][col] = width;
    setColWidth([...colWidth]);
  };

  return (
    <View>
      {/* Map along rows */}
      {data.map((item, rowIndex) => (
        <View
          key={rowIndex}
          style={{
            flexDirection: 'row',
            maxWidth: maxSize.width,
          }}>
          {/* Map along columns */}
          {Object.keys(item).map((key, colIndex) => (
            <View
              key={key}
              onLayout={event => {
                onLayout(event, rowIndex, colIndex);
              }}
              style={{
                minWidth: Math.max(...colWidth.map(row => row[colIndex])),
                flexShrink: 1,
                ...rowStyle,
              }}>
              {item[key]}
            </View>
          ))}
        </View>
      ))}
    </View>
  );
};

export default Table;

Here's how it looks with some example data from my project (the yellow boxes are the tables using this code):

Android emulator showing the working code

Right now, the only problem I can see is that it doesn't update when rotating from landscape to portrait (but portrait to landscape works fine???).

  • Related