Reputation: 1722
Problem: I would like to have a React Virtualized table that can take up a maximal space in height (let's say 500px
). Immediately below the last visible row of the table, I would like to add another element. Somehow like this:
If the content of the table is shorter than 500px
+-----------------------------------------+ ----+
| Header1 | Header2 | ... | |
+---------+---------+---------------------+ | less than 500px,
| row1 | row1 | row1 | | say 200px
+---------+---------+---------------------+ |
| row2 | row2 | row2 | |
+---------+---------+---------------------+ ----+
Other content
If the content of the table is longer than 500px
+-----------------------------------------+^ ----+
| Header1 | Header2 | ... || |
+---------+---------+---------------------+| | 500px,
| row1 | row1 | row1 || | notice the
+---------+---------+---------------------+| | scroll bar
| row2 | row2 | row2 || |
+---------+---------+---------------------+| |
| row3 | row3 | row3 || |
+---------+---------+---------------------+| |
| row4 | row4 | row4 |V ----+
Other content
In other words: there should be no white gap between the table and "Other content", but also the table should take up a maximum of 500px.
Question: How can I achieve this?
My attempt: I've tried with AutoSizer, but the issue is:
if the AutoSizer
is contained in a div
with only a fixed height (e.g.: height: 500px
), then there will be a white gap between the table and "Other content" if the table is too short; so what I get is this:
+-----------------------------------------+ ----+
| Header1 | Header2 | ... | |
+---------+---------+---------------------+ | less than 500px,
| row1 | row1 | row1 | | say 200px
+---------+---------+---------------------+ |
| row2 | row2 | row2 | |
+---------+---------+---------------------+ ----+
|
| gap of
| 300px
|
|
Other content ----+
if the AutoSizer
is contained in a div
with only a maximal height (e.g.: max-height: 500px
), then only the header is shown, and the "Other content" will be above the header (i.e. the containing div
does not grow as the table would grow, and thus AutoSizer
does nothing); what I get is this:
Other content ----------------------------+
| Header1 | Header2 | ... |
+---------+---------+---------------------+
(If I give both maximal and fixed height, then of course the fixed height "wins", and the result is the same as in the first case.)
Remark: I'm not convinced at all, that AutoSizer
is the correct approach here, so I'm open to any other suggestion (if possible, without further third-party dependencies, but that is ok too, if there is no other way).
Source for reference:
<div>
<div style={{height: "100px", maxHeight: "500px"}}>
<AutoSizer>
{({height, width}) => (
<Table
ref="Table"
disableHeader={false}
headerClassName=""
headerHeight={40}
height={height}
rowClassName=""
rowHeight={40}
rowGetter={rowGetter}
rowCount={6}
width={width}>
<Column
label="Index"
cellDataGetter={({rowData}) => rowData.index}
dataKey="index"
width={60}
/>
<Column
label="Text1"
cellDataGetter={({rowData}) => rowData.text1}
dataKey="text1"
width={90}
/>
<Column
label="Text2"
cellDataGetter={({rowData}) => rowData.text2}
dataKey="text2"
width={90}
/>
</Table>
)}
</AutoSizer>
</div>
<div>Some other text</div>
</div>
Clarification of constraints: The solution has to work with Virtualized-React table. It is not an option to use HTML table. (Above I only said that maybe AutoSizer
is not the right solution, but I do need react table.)
Suggested approach in the answer: I tried to apply @jered's suggestion to my case, and again, got a table which extends to the full max-height. I'm not sure if I did something wrong, or if the suggested approach does not work at all for this situation
<div style={{ display: 'flex',
border: '1px solid red',
flexDirection: 'column' }}>
<div style={{
display: 'flex',
maxHeight: '500px',
border: '1px solid blue',
padding: '2px',
margin: '2px',
overflow: 'scroll'
}}>
<Table
ref="Table"
disableHeader={false}
headerClassName=""
headerHeight={40}
height={900}
rowClassName=""
rowHeight={40}
rowGetter={rowGetter}
rowCount={6}
width={400}>
<Column
label="Index"
cellDataGetter={({rowData}) => rowData.index}
dataKey="index"
width={60}
/>
<Column
label="Text1"
cellDataGetter={({rowData}) => rowData.text1}
dataKey="text1"
width={90}
/>
<Column
label="Text2"
cellDataGetter={({rowData}) => rowData.text2}
dataKey="text2"
width={90}
/>
</Table>
</div>
<div style={{ display: 'flex',
flexDirection: 'column',
border: '1px solid green',
padding: '2px',
margin: '2px' }}>Some other text</div>
</div>
Related question: Basically React Virtualized table creates a div
with 0px x 0px
size and visible overflow of its content. Thus, if the size of the overflow can be considered, this would solve the issue. This is an interesting question in itself, so I'll open a follow-up, but leave this one open, as there might be a better solution for this specific case.
One more update: I noticed that the zero-size div is not created, if I put the table inside a flex-container.
Upvotes: 5
Views: 6258
Reputation: 31
I was able to fix this by disabling height in Autosizer and calculating it manually like this:
const ROW_HEIGHT = 40
const MAX_HEIGHT = 500
<AutoSizer disableHeight>
{({ width }) => (
<List
width={width}
height={Math.min(MAX_HEIGHT, ROW_HEIGHT * data.length)}
rowCount={data.length}
rowHeight={ROW_HEIGHT}
rowRenderer={rowRenderer}
overscanRowCount={10}
/>
)}
</AutoSizer>
Upvotes: 1
Reputation: 347
I was able to accomplish this for React virtualized List using rowRenderer and refs, but I believe it should work with Table as well.
const [listHeight, setListHeight] = useState(500);
<List height={listHeight} .../>
const rowRefs = useRef(dataInList.map(() => React.createRef<HTMLDivElement>()));
const rowRenderer = useCallback(
({ key, index, style, parent }: ListRowProps) => {
const item = dataInList[index];
return (
<CellMeasurer
key={key}
cache={cache.current}
columnIndex={0}
parent={parent}
rowIndex={index}>
<div ref={rowRefs.current[index]}>
Your row info goes here
</div>
</CellMeasurer>
);
},
[formatted]
);
offsetHeight
and set the state listHeight to that or whatever max height you want (whichever is less of the two).useEffect(() => {
const domListHeight = rowRefs.current
.filter((e: any) => e.current)
.reduce((acc: any, e: any) => acc + e.current.offsetHeight, 0);
const MAX_HEIGHT = 500;
const usableHeight = domListHeight < MAX_HEIGHT ? domListHeight : MAX_HEIGHT;
setListHeight(usableHeight);
}, [dataInList]);
Summary: The component will render the list at the max height and then calculate the actual height of all the rows combined, if that total is less than the max height intended, the component will re-render the list from scratch with that new lower height.
Upvotes: 0
Reputation: 11571
No need to resort to React or anything complicated. Just use FlexBox in simple CSS.
Give the container of your "Table" and "Other content" display: flex
and set a max-height
on the inner element that wraps the <table>
. FlexBox children will try to expand to fill as much space as their contents require and no more, but won't expand past their max-height
if set.
Note: Expand snippet to full screen to see the full effect.
function adjustrows() {
let val = document.getElementById("slider").value;
let table = document.getElementById("table");
let inner = `
<tr>
<th>foo</th>
<th>bar</th>
<th>baz</th>
<th>foo</th>
<th>bar</th>
<th>baz</th>
</tr>
`;
for (let i = 0; i < val; i++) {
inner += `
<tr>
<td>foo</td>
<td>bar</td>
<td>baz</td>
<td>foo</td>
<td>bar</td>
<td>baz</td>
</tr>
`;
}
table.innerHTML = inner;
document.getElementById("numrows").innerText = val;
}
adjustrows();
table, tr, th, td {
box-sizing: border-box;
border: 1px solid gray;
}
table {
flex-grow: 1;
}
th {
background: lightgray;
}
label {
display: flex;
justify-content: center;
margin: 5px;
}
#numrows {
margin: 0 10px;
}
#flex-container {
display: flex;
border: 1px solid red;
flex-direction: column;
}
#table-container {
display: flex;
max-height: 500px;
border: 1px solid blue;
padding: 2px;
margin: 2px;
overflow: scroll;
}
#other-content-container {
display: flex;
flex-direction: column;
border: 1px solid green;
padding: 2px;
margin: 2px;
}
.placeholder {
background: darkgray;
margin: 10px;
height: 120px;
}
<div id="flex-container">
<label>
<strong>Number of table rows:<span id="numrows"></span> </strong>
<input id="slider" onchange="adjustrows();" oninput="adjustrows();" type="range" min="0" max="100" value="1" />
</label>
<div id="table-container">
<table id="table">
</table>
</div>
<div id="other-content-container">
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
</div>
</div>
Note that if you delete some of the <tr>
elements from the table so that it is shorter than 500px
, #table-container
will simply shrink down to accommodate.
As always, Mozilla Developer Network has some pretty good info on FlexBox.
Upvotes: 0