Tabbing to First Element when react-virtualized is scrolled to the bottom - reactjs

I am using react-virtualized library. And I need to be able to tab through the list.
Since not all the items are rendered, I am not able to tab to the last row
Similar issue will happen if I want to tab to the first row when virtual list is scrolled to the bottom of the list
If I was able to always render first row and last row, no matter scrolling position, I would be able to tab to the last and first element.
Use case:
Imagine current focus is on input box which is situated above the virtual list, and imagine virtual list is scrolled to the bottom.
If I press on my keyboard tab I expect my focus to be on the first element. But because the first element is not rendered,the focus will be on the first rendered row by virtual list. I hope this makes sense.
This is my current code:
class Locations extends React.Component {
setListElementRef = (ref) => {
this.listRef = ref;
}
rowRenderer = ({ index, style }) => {
return (
<div style={style} key={index} >
<CheckBoxContainer />
</div>
);
}
render() {
const {
filteredLocations, onSearchValueChange,
} = this.props;
return (
<List
tabIndex={-1}
isScrollingOptOut
ref={this.setListElementRef}
rowRenderer={this.rowRenderer}
rowCount={filteredLocations.length}
rowHeight={32}
height={300}
width={218}
overscanRowCount={5}
/>
);
}
}

Related

react-virtualized: how to use a separate rowRenderer for height measurement

I'm trying to display large text documents with complex markups using the react-virtualized List component. The document is broken up into chunks of text of varying lengths shorter than some maximum. The List component renders each of these chunks as a row.
Since I can't use fixed row heights because chunk lengths vary, I'd like to use CellMeasurer. The problem is that the parsing needed to generate the markups on each chunk is expensive -- this is part of the reason I want to use react-virtualized. Even if all the chunks are rendered in the background, it will still be too slow.
Since the markup does not affect height, I'd like to use a simpler rowRenderer function that renders text without markup only for measuring rows, and then provide a separate and more complete rowRenderer for the actual render of each chunk with markup. Is there a way to do this?
This is admittedly really hacky, and not best practices, but could you do something like:
<List
rowHeight={(index) => {
const rowData = data[index];
let div = document.getElementById('renderer');
if (!div) {
div = document.createElement('div');
div.id = 'renderer';
}
ReactDOM.render(div, <Row>{rowData}</Row>);
const height = div.offsetHeight;
ReactDOM.unmountComponentAtNode(div);
if (index === data.length - 1) {
div.parentNode.removeChild(div);
}
return height;
}}
/>
Or, actually, you could have two lists, one with visibility: hidden, where you just render each row without markup, get the height, and add it to an array. Once the length of the array is equal to your data length, you no longer show it, and then render the other one, with rowHeight={index => heights[index]}
After some trial and error I found a good way to do this is to make the rowRenderer function decide which way to render the row. You can do this by checking the _rowHeightCache property in the CellMeasurerCache instance use use in your list. Note that the keys of _rowHeightCache take the form: "index-0" where index is the row's index.
Here is how you can set up the row renderer:
TextRowRenderer(
{
key, // Unique key within array of rows
index, // Index of row within collection
// isScrolling, // The List is currently being scrolled
style, // Style object to be applied to row (to position it)
parent, // reference to List
},
) {
const { textArray } = this.props;
// get the cached row height for this row
const rowCache = this.listCache._rowHeightCache[`${index}-0`];
// if it has been cached, render it using the normal, more expensive
// version of the component, without bothering to wrap it in a
// CellMeasurer (it's already been measured!)
// Note that listCache.defaultHeight has been set to 0, to make the
// the comparison easy
if (rowCache !== null && rowCache !== undefined && rowCache !== 0) {
return (
<TextChunk
key={key}
chunk={textArray[index]}
index={index}
style={style}
/>
);
}
// If the row height has not been cached (meaning it has not been
// measured, return the text chunk component, but this time:
// a) it's wrapped in CellMeasurer, which is configured with the
// the cache, and
// b) it receives the prop textOnly={true}, which it tells it to
// to skip rendering the markup
return (
<CellMeasurer
cache={this.listCache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{() => {
return (
<TextChunk
key={key}
chunk={textArray[index]}
index={index}
style={style}
textOnly
/>
);
}}
</CellMeasurer>
);
}
This is then passed to the List component in the ordinary way:
<List
height={pageHeight}
width={pageWidth}
rowCount={textArray.length}
rowHeight={this.listCache.rowHeight}
rowRenderer={this.TextRowRenderer}
deferredMeasurementCache={this.listCache}
style={{ outline: 'none' }}
ref={(r) => { this.listRef = r; }}
/>
By including a reference callback, we can now access the
measureAllRows method, which we can use to force the List
to render all rows in advance to get the heights. This will
ensure that the scroll bar functions properly, but even with
the textOnly flag can take a while longer. In my case, I believe
it is worth the wait.
Here is how you can call measureAllRows:
componentDidUpdate() {
const {
textArray,
} = this.props;
// We will only run measureAllRows if they have not been
// measured yet. An easy way to check is to see if the
// last row is measured
const lastRowCache = this
.listCache
._rowHeightCache[`${textArray.length - 1}-0`];
if (this.listRef
|| lastRowCache === null
|| lastRowCache === undefined
|| lastRowCache === 0) {
try {
this.listRef.measureAllRows();
} catch {
console.log('failed to measure all rows');
}
}
}
The try-catch block is needed because if it tries to measure
before the List is constructed it will throw an index-out-of
-bounds error.
FINAL THOUGHTS
Another possible way to do this would be to have the actual List
component be conditional on cached measurements rather than the
rowRenderer function. You could render an array of text as a a
regular list, wrapping each row in a <CellMeasurer>. When the
cache is filled, you would then render a virtual List configured
using the prefilled cache. This would obviate the need to call
measureAllRows in componentDidUpdate or a useEffect callback.

How to place a box below one of the elements present on screen on click when the elements were present by 50 cards using .map() function

I have placed 50 elements on the screen using data.map()
When I click a element, it should open a Component below the clicked element. How should i do so?
your question could have been more clear. Use react state lift
basically you should have a state variable like selectedCard and store its key or something unique for each card.
So when you click on the card onClick={() => this.setState({ selectedCard: cardKey })
and in map function check while rendering if (cardKey === selectedCard) show the component in position: absolute and specify the absolute style properties so that it ll be displayed below your selected card.
Have a state variable that has a unique identifier (probably id or some key) of the clicked element. Then whenever an element is clicked update the state with clicked element's unique Id.
And conditionally render the box for matching element.
const [activeElement, setActiveElement] = useState(null);
.....
elements.map(element => (
<div key={element.uniqId} onClick={() => setActiveElement(element.uniqId)}>
// element jsx
{activeElement === element.uniqId && (
// box jsx
)}
</div>
))

React-data-grid : Changing a row color depending on a value of the row

My component Test is called and gets in props an object containing datas from a mysql request (Select * from db.mytable). I store this.props.data in a state and render it in a ReactDataGrid.
What I'm trying to do is to update and change color of a row when a value of a cell is changed in my ReactDataGrid. For example, if a user right clicks on a rows, a context menu appear and he has the choice between Check and Uncheck. When he clicks on Check, i want to update the row and make it appear green.
I've got a database where I store the Checked state so when the user refreshes the page, the line should stay green.
How may I do this?
So when I click check in the context menu this function is called, it will update my state table containing the rows :
//right click then check
rowCheck = (rowIdx) => {
var tableState=this.state.tableState.concat([])
tableState[rowIdx.rowIdx].CHECKED='Y'
this.setState({tableState})
}
the RowsRenderer function :
RowRenderer = ({ renderBaseRow, ...props }) => {
const color = this.state.tableState[props.idx].CHECKED==='Y' ? "blue" : "";
return <div style={{color}}>{renderBaseRow(props)}</div>;
};
the data-grid :
<ReactDataGrid
columns={this.state.column}
rowGetter={i => this.state.tableState[i]}
rowsCount={this.state.tableState.length}
minHeight={500}
enableCellSelect={true}
onGridRowsUpdated={this.onGridRowsUpdated}
cellNavigationMode={'changeRow'}
rowRenderer={this.RowRenderer}
contextMenu={
<ExampleContextMenu
onRowCheck={(e, {rowIdx})=>this.rowCheck({rowIdx})}
onRowUncheck={(e, { rowIdx }) => this.rowUncheck({rowIdx})}
/>
}
RowsContainer={ContextMenuTrigger}
rowSelection={{
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: this.onRowsSelected,
onRowsDeselected: this.onRowsDeselected,
selectBy: {
indexes: this.state.selectedIndexes
}
}}
/>
Adding to the above solution:
I wanted to change the row's background-color rather than the color itself so I ended up adding a class to the div wrapper instead of style.
<div className={classnames({ 'focused-row': focused })}>{data.renderBaseRow(data)}</div>
Then in my css I added
.focused-row .react-grid-Cell {
background-color: #f5deff !important;
}
Otherwise the cell's background-color was overriding the row's

Using SwipeableViews with updateHeight()

A new update to SwipeableViews allows for each Tab in the MaterialUI Tabs Component to have their own height. Let me back up a little. Before, MaterialUI Tabs component will show all tabs in the height of the longest tab. This created unnecessary scrolling on tabs that didn't need it. SwipeableViews recently fixed this by adding this to their component
<SwipeableViews
action={actions => {this.swipeableActions = actions;}}>
<div>{'slide n°1'}</div>
<div>{'slide n°2'}</div>
<div>{'slide n°3'}</div>
</SwipeableViews>
and
componentDidUpdate() {
this.swipeableActions.updateHeight();
}
That fixed the height issue across tabs on their loaded state. But when items are hidden and shown with a click event, the height persists and shown items do not show (being cut off from view)
See image (imgur failed to load the image) see the image link instead
image
You need to add:
animateHeight
to the SwipeableViews component.
You may also have to pass a function down to child components in order to update the height when a child modifies the view.
UpdateSwipeableViewHeight = () => {
if (this.swipeableActions) this.swipeableActions.updateHeight()
};
<SwipeableViews
id="searchTabsSwipeableView"
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeTabIndex}
onChangeIndex={index => {
this.handleChange(null, index);
this.UpdateSwipeableViewHeight();
}
}
action={actions => (this.swipeableActions = actions)}
animateHeight
>
<div className={classes.swipableViewsComponent}>
<ChildComponent updateHeight={this.UpdateSwipeableViewHeight} />
</div>

React - scrolling to the bottom of a flex container without scrolling the whole page

I'm using react and trying to build a facebook like chat - where the right column scrolls down and has a list of threads, and the middle column had the thread content and scrolls bottom up.
I have it all setup using flex, but am stuck on getting the middle column to scroll to the bottom by default (since I want to see the latest chat message). Here is the snippet for the actual container (I'm using React bootstrap):
<Row >
<Col ref={ (node) => { this.cont = node }}>
{this._buildThread()};
</Col>
</Row>
and here is the snippet for my ComponentDidMount:
componentDidMount() {
MessageStore.addChangeListener(this._onChange);
MessageService.getThread(this.props.id, 1000, 1, false);
this.cont.scrollTop = this.cont.scrollHeight;
}
Where cont is the ref for the container div that holds my messages. Yet nothing happens - it doesn't scroll, and if I look at node.scrollTop after setting it, it remains at 0 - seems immutable.
Any help would be much appreciated. Thanks!
Not sure about the details of your CSS, but I just had a similar-looking issue. The solution in my case was to make sure that the element itself would scroll and not its container:
.containerIWantToScroll {
overflow-y: auto;
}
If the element's container expands to fit the element, then that element will not scroll, thus its scrollTop value is always zero.
When are you triggering the code you included? To scroll to the bottom on render, I would write something like the following, calling triggerScroll in componentDidMount:
class App extends React.Component {
componentDidMount () {
this.triggerScroll();
}
triggerScroll () {
this.cont.scrollTop = this.cont.scrollHeight;
}
render () {
return (
<div>
<div className='containerIWantToScroll' ref={ (node) => { this.cont = node } }>
Content
</div>
</div>
)
}
}

Resources