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:
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???).