Sticky headers in react-virtualized - reactjs

I am using a List component in react-virtualized to render a large number of items. In my implementation, the items are sectioned, and I want the section headers to be sticky so that the current section remains visible as users scroll down. Essentially, I need react-virtualized NOT to destroy the section headers as the scroll position changes (but continue to destroy other items). Is there any way to do this right now? I'm open to hacks as long as they aren't too crazy.

We had similar requirements to you - we need a list that was sectioned with support for sticky headers. We could not achieve this with react-virtualized Lists/Grids, so I created https://github.com/marchaos/react-virtualized-sticky-tree which supports sticky headers.
See example here.

If I understood your question correctly, you would like to have sticky header a la a spreadsheet. You can do that with the ScrollSync component, have a look at the demo/docs.
Here is the example displayed in docs:
import { Grid, List, ScrollSync } from 'react-virtualized'
import 'react-virtualized/styles.css'; // only needs to be imported once
function render (props) {
return (
<ScrollSync>
{({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) => (
<div className='Table'>
<div className='LeftColumn'>
<List
scrollTop={scrollTop}
{...props}
/>
</div>
<div className='RightColumn'>
<Grid
onScroll={onScroll}
{...props}
/>
</div>
</div>
)}
</ScrollSync>
)
}

In case anyone came here using react-virtualized's Table component instead of the List component, you can make the header sticky with the following CSS:
.ReactVirtualized__Table__headerRow {
position: sticky;
inset-block-start: 0;
z-index: 1;
}
Make sure none of the Table's parent elements have overflow styling, otherwise this won't work.

Related

Append components to a fixed list using react-window?

I have a fixed list and grid using react-window, infinite loader, and auto sizer. It's working fine but I'm looking to append a component before the list/grid starts (for example, a search box and a button). What's the correct approach to accomplish this? I want this component to scroll with the fixed list and not scroll separately. I've tired just rendering the component before but then it's not in the fixed list container and doesn't scroll with it.
{/* WANT TO ADD SOME SORT OF COMPONENT HERE SO IT CAN SCROLL WITH LIST */}
{/* CAN'T IN HERE BECAUSE ITS NOT INSIDE THE FIXED CONTAINER SO IT SCROllS SEPARATELY */}
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader
isItemLoaded={index => index < stateData.data.length}
itemCount={stateData.data.length + 1}
loadMoreItems={loadMoreProducts}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
onItemsRendered={onItemsRendered}
ref={ref}
height={height}
itemCount={stateData.data.length + 1}
itemSize={350}
width={width}
>
{/* WANT TO ADD SOME SORT OF COMPONENT HERE SO IT CAN SCROLL WITH LIST */}
{/* CAN'T IN HERE BECAUSE ITS LOOPING LISTITEM */}
{ListItem}
</FixedSizeList>
)}
</InfiniteLoader>
)}
</AutoSizer>
Edit: Pretty much I don't want the list (or grid) and the actual page to be two different scroll containers. I'd like the whole page to scroll together. I've come across this issue because some of my containers need to have an infinite list of items users can scroll through so the list needed to be virtualized to improve performance.
See a demo here. Really the fixed container should just be considered the whole page and the search box and everything else should scroll with the infinite list. Having two different scroll containers isn't too great for the ux.
If you want the whole page to scroll with your virtual list you will need a window scroller. This also involves playing around with some styling as well. To make your searchbox scroll with your list you will need a position of fixed. A spacer div and some styling helps the illusion.
I added a basic window scroller and some styling changes to your codepen here.
A few options:
Make a wrapper around The autosizer, that holds both your search box and the autosizer.
<>
<SearchBox />
<AutoSizer>
... your items
</AutoSizer>
</>
let the AutoSizer contain both the search and the list
<AutoSizer>
<SearchBox />
<List>
... your items
</List>
</AutoSizer>
Since your infinite scroll is using a render function you might need fragments
<AutoSizer>
<InfiniteScroll>
{({ onItemsRendered, ref }) => (
<>
<SearchBox />
<List>
... your items
</List>
<>
)}
</InfiniteScroll>
</AutoSizer>
Here I edited your example and made it work using this approach.
Please note that the page scrolls only because of the top and bottom bars; which is the behaviour in your example.
Let the infinite loader populate an array, and append an extra item to that. The item can have a certain type. Assuming the FixedSizeList is the List component from react-virtualized then you can use it's rowRenderer to render the item of type "search" differently than the other items. Something like:
function rowRenderer ({ key, type, value, ...rest }) {
if (type === 'search') {
return <Search key={key} placeholder={value} {...rest} />
}
return <div key={key} {...rest}>{value}</div>
}
// Render your list
ReactDOM.render(
<AutoSizer>
{({ height, width }) => (
<List
height={height}
rowCount={list.length}
rowHeight={20}
rowRenderer={rowRenderer}
width={width}
/>
)}
</AutoSizer>,
document.getElementById('example')
);
Perhaps it also helps to look at the simple example from Vaughn.

React how to create custom overflow ellipsis

I would like to create a top navigation with a variable amount of items. All items that don't fit into the bar should not be rendered. There will be a little "..." icon that shows a sub-menu when you hover over it, that display the remaining nav items. Any ideas on how to achieve this? I can't seem to render the nav items conditional to the parents width.
I tried setting a ref on my navigation bar and check the scroll width against the client width but it didn't work. Even if I got it to work, I would still render one item more than I should because I don't know the width of the children beforehand.
<div ref={ref} className="nav-list">
{ref ? items.map(i => {
return ref.scrollWidth <= ref.clientWidth ? (
<div className="nav-item" key={i.id}>
{i.title}
</div>
) : false;
}) : false}
<div className="nav-remaining">
<div>
//render remaining items
</div>
</div>
</div>
I saw someone implementing this by adding the children recursively and then backtrack to remove all children that are overflowing. But I have now idea on how to implement this approach in react.

Semantic UI React side bar render pusher only on visibility change (redux/rematch)

I am using react and semantic. I am using the multiple sidebar example. The idea is that the left hand sidebar offers up some menu options, and then the right hand sidebar is the sub menu based on which option from the left menu is chosen. When a sub menu item is selected, a component is added to the Sidebar.Pusher, i.e displayed on the page.
It all works except re-rendering the content of the Sidebar.Pusher. This apparently only updates when the left hand side bar's visibility changes. I am using redux/rematch to handle state, and can see that the state that holds the content of the Sidebar.Pusher is being updated, but `render() is only being called when visibility changes of the sidebar.
The content of Sidebar.Pusher is an array, and I even tried displaying on the page the length of the array, which is being updated (pushed into) each time an item on the right hand sidebar is clicked. However this doesn't cause a render() to be fired, its literally when the left hand sidebar visibility changes.
Just to note, I did see this issue, however its from last year, and the answer wasn't enough for me to be able to fix the issue. Help would be appreciated.
Structure:
Index.js renders App.js, App.js renders Menu.js (which is a semantic set of tabs). One of the menu options is Sidebar.js which renders:
<Sidebar.Pushable as={Segment}>
<Sidebar
as={Menu}
animation="overlay"
direction="right"
inverted
vertical
visible={secondaryVisibility}
width="wide"
>
{focusedList.map((el, i) => {
return (
<Menu.Item key={i} as="a" onClick={() => this.addSegment(el)}>
<Article el={el} />
</Menu.Item>
)
})}
</Sidebar>
<Sidebar
as={Menu}
animation="overlay"
icon="labeled"
inverted
// onHide={this.handleSidebarHide}
vertical
visible={primaryVisibility}
width="wide"
>
<Menu.Item
onClick={() => this.changeTab(menuItem)}
as="a"
name="menuItem"
header
>
Menu Item
</Menu.Item>
</Sidebar>
<Sidebar.Pusher style={{ minHeight: "600px" }}>
<Segment basic>
{segments.map((el, i) => {
console.log(`el ${el}`)
return <Content key={i} segment={el} />
})}
</Segment>
</Sidebar.Pusher>
and all state (secondaryVisibility etc) is stored in rematch
Thanks
I haven't been able to identify the problem based on the code you've posted, could you provide more info such as the entire Sidebar.js and maybe what's in the Content component?. My guess would be that there's a HOC or lifecycle method getting in the way.
I've created a trivial example that seems to work fine, if I understand what you're trying to accomplish: https://codesandbox.io/s/myl6xpz9py
I got it. I forgot about immutability in state. Perhaps someone will benefit from this.
I was trying to update a state array with
let tmp = prevState.contract.segments
tmp.push(segment)
this.update({ segments: tmp })
However, this won't work as tmp is a reference to prevState.contract.segments, so this won't work, as pushing to tmp is equivelent to pushing to prevState.contract.segments.
you have to have a completely new array:
const tmp = [...prevState.contract.segments, segment]
this.update({ segments: tmp })
Now it works.

scrollIntoView doesn't scroll to bottom when there is an image/file in the container

I have a react app that has a commenting section. Users can comment pdfs, images, excel & csv files. When a user opens up a project I want the container with comments to scroll to the very bottom. The functionality works perfectly if the comments are only text. However, when someone adds any sort of file in, it will no longer scroll all the way to the bottom. It only scroll about 3/4 and the more files added, the less it scrolls.
This is where its called:
componentDidUpdate() {
this.scrollToBottom()
}
scrollToBottom(){
this.el.scrollIntoView({ behavior: 'smooth' })
}
This is whats in my render:
<div className="comments-container-parent">
{this.props.projectComments.map((comment) => {
return <ProjectComment editCommentText={this.props.editCommentText} commentId={comment.id} deleteComment={this.props.deleteComment} comment={comment} projectData={this.props.projectData} />
})}
<div style={{ float:"left", clear:"both" }} ref={el => { this.el = el }}></div>
</div>
How can I get the scroll to work properly when files are involved?
I ran into this recently and the solution was to make sure that there is a container (div or whatever) with a fixed height set. Setting the height dynamically or based on some logic in a useEffect hook doesn't work. The height of the images must be set from the start so that when the list renders, the height of the list doesn't change.

Positioning react-bootstrap popover

I have a couple of bootstrap rows. When the user clicks on any of the rows, a popover should appear in the middle of the row and show some information. However, at the moment it pops far right, after the end of the row.
Here is my JSBin link showing the code: https://jsbin.com/rogeyufuku/1/edit?css,js,output
I tried to reposition it by setting positionLeft to a negative value as follows:
<ReactBootstrap.Popover className="my-popover"
positionLeft={-300}
positionTop={20}
>
but that did not work.
I also tried to manipulate it using plain old CSS rules as follows:
.my-popover {
position: relative;
left: -200px;
}
but that did not work either.
Is there any idea to render the popover overlay as I desire it?
(Including all the code here for reference, but JSBin link is working)
EDIT: Here is how it looks now: http://i.imgur.com/URuYthN.png
Here is what I want it to look like: http://i.imgur.com/M4Hh1wI.jpg
its a bit hard to see what you are trying to do but you shouldn't be using the positionLeft and positionTop props, they are going to be set by the Overlay component
If you want the popover to appear in the middle, use placement prop and the values "top", or "bottom"instead of "right"
If you want finer grained control over where and how it is positioned you will need to make your own custom Popover component that does something with the positionLeft and positionTop component that are passed in to it by the Overlay component
class MyPopover extends React.Component {
render(){
var left = calculateMyOwnLeftPosition()
return (
<ReactBootstrap.Popover {...this.props} positionLeft={left}>
{ this.props.children }
</ReactBootstrap.Popover>
)
}
}
and then you'd use it like:
<Overlay>
<MyPopover>sweet content</MyPopover>
</Overlay>
Use Popover props positionLeft and positionTop:
<Popover
id="popover-basic"
placement="right"
positionLeft={200}
positionTop={50}
title="Popover right"
>
But if you're using an OverlayTrigger use placement on OverlayTrigger:
React-Bootstrap Popovers With OverlayTrigger:
const popoverTop = (
<Popover id="popover-positioned-top" title="Popover top">
<strong>Holy guacamole!</strong> Check this info.
</Popover>
);
<OverlayTrigger trigger="click" placement="top" overlay={popoverTop}>
<Button>Holy guacamole!</Button>
</OverlayTrigger>

Resources