I have the following requirements to my table:
Table should have fixed header
It needs to autosize: for infinite scroll to work the first fetch should get sufficient amount of data for scroll to appear at all.
table body should work as infinite loader: when scrolled to the end of the list table body should show loading indicator and load more rows
My assumptions are as follows:
as user will scroll through possibly large sets of data I should virtualize lists (react-virtualized seems to be the only good option for me)
as we currently have react-table I want to keep it (it has great mechanism of declaring table rows, columns, accessing data and filtering + sorting)
As we use material ui I need to use material ui react components
Because react-virtualized has own Table component I could use it, but react-table has different way of rendering rows and columns, therefore I have to use List component. (react-table separates rows and columns while react-virtualized uses columns directly as children of Table component)
I saw that react-virtualized works with HOC component called InfiniteLoader, so I should use that as well
Finally I need my columns to not be messed up just because it has more text (i.e. have dynamic height). So I tried to use CellMeasurer for this.
What I was able to achieve can be seen in this sandbox
https://codesandbox.io/s/react-table-infinite-mzkkp?file=/src/MuiTable.js
(I cannot provide code here because it is quite large)
So, in general I could make Autosizer, CellMeasurer and List components from react-virtualized to work.
I am stuck at inifinite scroll part. I saw the example on official docs, but it seems to be a little bit anti pattern (mutation state directly is not a good thing at all)
So I tried to achieve similar result, however if you could see, my loadMore function fires too early for some reason. It leads to requests being sent on nearly every scroll event.
Any help is much appreciated.
What I tried already:
Using react-window instead of react-virtualized
It works only for simple use cases, and fails with dynamic size of cells.
Using react-inifnite-scrollcomponent (https://www.npmjs.com/package/react-infinite-scroll-component)
It is working for entire page (unable to make "sticky" header, unable to render loading indicators as part of table body, it odes not have any optimization for long lists)
Using Table component from react-virtualized.
I was unable to make it work with react-table (as Table component from react-virtualized seems to render Cells directly as children of Table component. I know it has renderRow function, but it means two separate places while react-table has
<TableRow
{...row.getRowProps({
style
})}
component="div"
>
{row.cells.map((cell) => {
return (
<TableCell {...cell.getCellProps()} component="div">
{cell.render("Cell")}
</TableCell>
);
})}
</TableRow>
Also, it is not clear how to render custom filters this way.
I know this is not the answer you were looking for, but I recommend using an IntersectionObserver instead of the virtualize library. react-virtualized is not great for dynamic/responsive sized elements. IntersectionObserver is a native browser api that can detect when an element enters the viewport, without providing it with element sizes.
You can easily use it with a react library like react-intersection-observer which has an InView component and a useInView hook, though that would require wrapping every table cell with InView. The performance of an IntersectionObserver tends to be better than a scroll event-based solution like react-virtualized, though that might degrade if you have 1000s of observers.
You could also use something like intersection-observer-admin, which will pool all your observers into one instance similar to the react SyntheticEvent. You'd want to integrate that into something like React.useContext or redux.
I've found the Virtuso package to be a great help for this. Works better out the box for dynamically sized list items.
I don't have a complete solution for you, but here are a few things that might help you in your quest:
Consider a static header instead of using position: sticky to keep the header row at the top.
(If you could get everything else in
react-inifnite-scrollcomponent to work, this would solve the sticky
header problem.)
A method I've used is to adjust for the size of the scrollbar and always include it (even if not needed) - this may or may not work in all cases:
// Header div
<div
style={{ width: 'calc(100% - 0.9em)' }} // accomodate for the width of the scrollbar
>
// header stuff
</div>
// list (simplified for clarity) - here I used react-window
<div>
<AutoSizer>
{({ height, width }) => (
<List
style={{
overflowX: 'hidden',
overflowY: 'scroll', // always display scrollbar
}}
>
{RenderRow} // function to return a complete react-table row
</List>
)}
</AutoSizer>
</div>
And here are some other ideas if that one doesn't work for you:
https://stackoverflow.com/a/63412885/6451307.
If you need horizontal scrolling, you might consider the above plus a wrapper to provide the horizontal scrolling separately.
Consider the table as a list of rows for virtualization purposes.
It sounds like you've already tried this some, but I definitely found it easiest to use the list virtualization tools and then render a row of cells for each list item.
You can see an example of that in my code above, too.
Consider an overlay for the loading indicator.
If it works with your UI needs, create a separate element for the loading indicator that overlays the table body (using absolute positioning and turning it on only when it's needed). It could be solid to hide the table data completely or have a translucent background. This would prevent it from causing problems with whatever is going on in the table/list itself.
(Some accessibility actions might need to be taken, too. Like hiding the table data from screen readers when the overlay is up, giving the overlay role="alert" or similar, etc. so consider that if you go this route.)
If you're not already doing it, consider using div's instead of trying to use table elements to allow for easier styling and more flexible element structure.
react-table adds the correct table roles so you can use div's and your table should still have the correct semantics as long as you apply the react-table functions properly - like {...getTableBodyProps()} and similar.
Hopefully, one or more of those will help you get closer to your goal. Good luck!
Related
I am quite new to coding and building my first React app. I have made good progress in styling and solving my other problems on my own, but I just cannot figure out this one: my rendered map obscures something else on the page. For styling I am using v5 of Material-UI.
To be clear, the map renders and I have no issue seeing my map in my app (sometimes), as well as having it geographically positioned to specific coordinates. The code looks like this for its specific grid:
<Grid container direction="row" rowSpacing={150}>
<Grid item xs={12} position="relative">
<MapsContainer />
</Grid>
</Grid>
Depending on the grid position property of the MapsContainer, the map either disappears completely (absolute and fixed), or just a sliver shows above the page footer unless rowSpacing is as obscene as the example code, and I lose footer functionality (no set property). When position is relative, I lose both header and footer functionality.
Regardless of the footer component position property, the map does not push it further down.
I hope this is not too convoluted to understand
*Edit: The problem came from the google-maps-react module. After pouring over it for 3 hours with a more skilled developer, not even he could figure it out, so we just broke it less by trying to reign in the div the rendered map would throw around itself. Is it fixed? Yes. Is it fixed in the proper way? Probably not. If you have the proper way to fix it, by all means share!
I created a draggable drag and drop table with draggable rows.
For the need of my project, i added multiple drop targets with multiple Droppable elements like in this example:
https://codesandbox.io/s/ql08j35j3q
It work pretty fine, but there is one problem, the scroll speed.
When i'm trying to drop an item in an element at the bottom of the page, it gets very slow.
This GIF will show the problem.
Do you have any clue for a solution ?
This may be a result of react-beautiful-dnd autoscroll, interfering with a css property called scroll-behavior. I just spent a day de-bugging this myself.
If you are using bootstrap, by default, bootstrap sets {scroll-behavior: smooth} to the entire html tag. To apply react-beautiful-dnd's fast auto-scroll, this css property should be {scroll-behavior: unset !important}
If you are not using bootstrap, or another library, check your css stylesheets, and see if {scroll-behavior: smooth} is set anywhere in any parent containers to your Droppables, and unset them.
A good way to debug this is by also opening Inspect Element in your browser, and looking at the styles applied to the html, body, or parent containers to your Droppables.
It appears that when scroll-behavior is defined in css or javascript( if you use window.scrollBy()), it may interfere with react-beautiful-dnd's fast auto-scroll feature, and make it slow.
Let me know if this works for you :) !
Here is my example in a gif - All the containers in the column are droppables
I have a working vertical column drag and drop React app using react-beautiful-dnd, but would like to 'invert' it so that Draggable items fall to the bottom of the Droppable div instead of floating to the top. Is this possible?
If I'm understanding you correctly, you'd like to have a list where when the container is not full, the items within that list are aligned to the bottom of the container?
If so, then yes - I would utilize flexbox.
Here is a good resource for flex - https://css-tricks.com/snippets/css/a-guide-to-flexbox/
There are multiple different ways to implement this with flex. Pick your poison.
edit
Based on the code sandbox:
I would add a wrapper div to the bay component with the same height as what you have now. On the child div, add a dynamic height field that is dependent on how many tables the bay contains.
Users won't be able to drop into the entire column but you could style it to show the user where they need to drop when the column is empty.
I ended up just creating another invisible strip with flexGrow = 1 to push everything to the bottom.
I want to create a table that can drag and drop both columns and rows on React. I've look at other solutions like react-beautiful-dnd, table-dragger, etc. The best one I've found so far is ag Grid but that would require me to get the license. I have been looking for other solutions out there and even trying to build this functionality myself in React. Does anyone have an insight on how to implement this functionality or even recommend other solutions that could help achieve this functionality.
There are different packages that you can use for this purpose that are open-source. No need to implement it yourself. Some examples that are worth looking into:
react-table-hoc-draggable-columns
react-sortable-hoc
material-table
dnd-kit helped me achieve column and row dragging. The trick was enabling column OR row dragging only when each is needed. It seems unlikely a UI would need a column and a row to drag simultaneously. So in my case if I hover on a column header dnd-kit uses horizontal dragging settings but if I hover on a row "header" then dnd-kit uses vertical dragging settings. That makes it appear like they can both happen when really it's one at a time.
Here are some relevant lines of example code to get started.
const [columnHover,setColumnHover] = React.useState(false);
<DndContext
modifiers={[columnHover ? restrictToHorizontalAxis : restrictToVerticalAxis]}
>
<SortableContext
items={columnHover ? columnIDs : rowIDs}
strategy={columnHover ? horizontalListSortingStrategy : verticalListSortingStrategy}
>
...
</SortableContext>
</DndContext>
I need to dynamically change the header row height of an ng grid, depending on which column headers need to be displayed. Some columns have a very long header and I want the column name to wrap so they don't need to be excessively wide. I also don't want a lot of blank space if I initially set a tall header height, but then don't need the space if those long column names aren't displayed.
The issue is I cannot get the headerRowHeight to dynamically change. It took some time to realize that I cannot even initialize headerRowHeight using a scope variable the same way as the other gridOptions (see line 23).
See plnkr
The reason this doesn't work is that ng-grid uses absolute positioning under the hood. Grid Options are only fired once, and then the heights are set in the html after that using style= on the html dom node. (THIS IS AWFUL!) They even set the style manually on all of the underlying header dom nodes.
The other avenue I thought of was trying to redraw the grid using ngGridLayoutPlugin. I played around with this for 30 minutes with no luck. You could try manually redrawing the page itself. It looks like ng-grid is not very good at redrawing the grid, and they've made optimizations specifically for updating data, but not the styles.
The 3.0 beta unstable release looks like they've made a lot of changes, and you'll be able to do what you're trying to do easier... however, it is not ready for production. See the header cell class conditionals in this example: http://ui-grid.info/docs/#/tutorial/115_headerCellClass
I had a hell of a time trying to get ng-grid styling to do my bidding at my last job. At a certain point, we were ready to toss it because it was too restrictive. Good luck.
Use this in your CSS.
.ui-grid-header-cell-primary-focus {
line-height: 2.428571;
}