React Virtualized Auto Scroll Issue - javascript

I am using react-virtualized v-9.21.2 to display a list, I am having an issue on insertion of a new item, upon inserting a new item to the list I am clearing the cache and updating the listkey to auto resize the height, otherwise, the new added item will be cropped but when the listkey get updated the list automatically scroll to top and this is not a desired behavior, my code is as follow:
UNSAFE_componentWillReceiveProps(nextprops) {
if (this.props.items.length !== nexprops.items.lenght)) {
// clear the cache and update the listKey
this.cache.clearAll();
this.virtualizedList && this.virtualizedList.recomputeRowHeights();
this.listKey += 1
}
renderItem = ({ index, key, parent, style }) => {
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={`CellMeasurerRow_${key}`}
parent={parent}
rowIndex={index} >
<div
key={`Item__${key}`}
style={style}
className='row'>
<Item
style={style}
key={`Item_${index}`}
/>
</div>
</CellMeasurer>
)
}
render(){
return (
<WindowScroller
key={`Scroller_${this.listKey}`}
ref={(e) => this.windowRef = e} >
{({ height, isScrolling, onChildScroll, registerChild, scrollTop, }) => (
<AutoSizer>
{({ width }) => (
<React.Fragment key={registerChild}>
<List
ref={`ListKey_${this.listKey}`}
autoHeight
isScrolling={isScrolling}
onScroll={onChildScroll}
key={this.listKey}
scrollTop={scrollTop}
height={height}
rowCount={this.props.items.length}
rowRenderer={this.renderItem}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
width={width}
overscanRowCount={10} />
</React.Fragment>
)}
</AutoSizer>
)}
</WindowScroller>
)
}
I tried programmatically to scroll to adjust the height without the update of the key, it worked but still not accurate, So, How can I update the virtualized with a new item and adjust the height without scrolling ??

If your data has a unique key, I think you can create a ListItem component add an useEffect hook calling the measure function when the data change. This may have performance impact.
function ListItem(props) {
useEffect(props.measure, [props.data.id]);
return (
<div style={props.style}>
{/* content render */}
</div>
);
}
renderItem = ({ index, key, parent, style }) => {
const item = getItem(index); // suppose item data structure: { id: unique_key }
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={`CellMeasurerRow_${key}`}
parent={parent}
rowIndex={index}
>
{(measure) => (
<ListItem
key={`Item__${key}`} style={style}
data={item}
measure={measure}
/>
)}
</CellMeasurer>
)
}

Related

How to make two rows inside one row react-virtualized?

I need to make for some rows react-virtualized from table, create 2 rows, 1 row it is row react-virtualized second row it is my custom row
Ho to do it, maybe has some example?
I tried use defaultTableRowRenderer() function,as far as I understand, this embeds newlines in react-virtualized table, but I not need this
its must look like this
table
My code look like this
return (
<>
<div style={{ height: "90vh" }}>
<AutoSizer>
{({ width, height }) => (
<TableVirtualized
headerHeight={70}
height={height}
overscanRowCount={5}
rowHeight={ 80}
rowCount={rowItems.length}
rowGetter={({index}) => rowItems[index]}
width={width}
className={className}
rowRenderer={rowRenderer}
>
{headerTitles.map((v:any, i:any)=>{
const key = Object.keys(v);
return (
<Column
label={v[`${key}`]}
dataKey={key}
width={200}
cellRenderer={(v: any)=> cell(v)}
key={i}
headerRenderer={()=> formatHeader(v[`${key}`])}
/>
)
}
)}
</TableVirtualized>
)}
</AutoSizer>
this my rowRender function. I think this is a bad way, because row react-virtualized height is fixed
const rowRenderer = (props: any) => {
const { index, style, className, key, rowData } = props;
if(rowData.attributes){
return (
<div className={cellProps.attributes.className}>
{defaultTableRowRenderer(props)}
<div className='blabla'>1</div>
</div>
)
}
return defaultTableRowRenderer(props);
}

Highlight One Item in a List at any given time (Index 0 by default)

Sorry for the horrible title, I couldn't for the life of me figure out how to word this problem. By default I want the first item in my list to be highlighted and others to be highlighted when the mouse is over them. This works fine and I was curious how I am able to remove the highlight of that initial item when another is highlighted, I know I need to handle the state within my parent component (List) but I cannot figure it out.
This is my list item that contains some basic state management to show whether the given item is highlighted or not.
export default function Item ({ title, onClick, selected, i }) {
const [highlight, setHighlight] = useState(false)
return (
<ListItem
i={i}
key={title}
selected={selected}
onClick={onClick}
onMouseEnter={() => setHighlight(true)}
onMouseLeave={() => setHighlight(false)}
// i === 0 && no other item is highlighted (need to get this state from the list component)
highlight={highlight || i === 0}
>
{title}
</ListItem>
)
}
Parent component that takes in a 'list' and maps each item in that list to the above component (Item):
export default function List ({ list, onClick, selected, render }) {s
return (
<div>
{render ? (
<ListContainer>
{list.map((item, i) => (
<Item
i={i}
key={item.title}
title={item.title}
onClick={() => onClick(item.title)}
selected={selected(item.title)}
/>
))}
</ListContainer>
) : null}
</div>
)
}
Here is a Gyazo link that shows the current implementation, what I want to achieve is for that initial item to no longer be highlighted when the mouse has entered another item where the index does not equal 0.
You need to lift your highlight state to parent List component
function List ({ list, onClick, selected, render }) {
const [highlightIndex, setHighlightIndex] = setState(0);
return (
<div>
{render ? (
<ListContainer>
{list.map((item, i) => (
<Item
i={i}
key={item.title}
title={item.title}
onClick={() => onClick(item.title)}
selected={selected(item.title)}
highlightIndex={highlightIndex}
setHighlightIndex={setHighlightIndex}
/>
))}
</ListContainer>
) : null}
</div>
)
}
function Item ({ title, onClick, selected, i, highlightIndex, setHighlightIndex }) {
return (
<ListItem
i={i}
key={title}
selected={selected}
onClick={onClick}
onMouseEnter={() => setHighlightIndex(i)}
highlight={i === highlightIndex}
>
{title}
</ListItem>
)
}
you can do it with css
ul:not(:hover) li:first-of-type,
ul li:hover {
background-color: red;
}
the first one will be highlighted while not hovered, and the other will be highlighted when hovered and "cancel" the first one.
Presuming the selected one will go to the top of the list.

ReactJS Function Component: accordion, allow for multiple selected open with useState

I've mocked a simple logic for an accordion collapsible panels in ReactJS. I'm trying to allow for multiple collapsible to be open but I'm not able to avoid all the collapsible to open and close at once no matter which collapsible has been clicked. This below is the logic for the accordion to allow only one collapsible at the time.
//Accordion.js
import React, { useState } from "react";
import styled, { css } from "styled-components";
import PropTypes from "prop-types";
import Collapse from "./Collapse";
import Header from "./Header";
const Accordion = ({ list, icon}) => {
const [isActiveIndex, setActiveIndex] = useState(null);
const toggleItem = index => {
setActiveIndex(isActiveIndex === index ? null : index);
};
return (
<Wrapper>
{list.map((item, index) => {
const checkOpen = isActiveIndex === index;
return (
<Container key={index}>
<Header
title={item.title}
icon={icon}
id={index}
onClick={toggleItem}
/>
<Body isOpen={checkOpen}>
<Collapse isOpen={checkOpen}>{item.content}</Collapse>
</Body>
</Container>
);
})}
</Wrapper>
);
};
I've created the whole mock in CodeSandBox here: https://codesandbox.io/s/1r2mvk87q
For the initial accordion I'm using useState and checking for the active index - for the allow multiple I guess I should check the previous state of the clicked item but I'm not able to pass the clicked item as the only target for the state to be checked.
//AccordionMultiple.js
const AccordionM = ({ list, icon }) => {
const [isOpen, setOpen] = useState(false);
const toggleItemM = index => {
setOpen(prevState => !prevState);
};
return (
<Wrapper>
{list.map((item, index) => {
return (
<Container key={index}>
<Header
title={item.title}
icon={icon}
id={index}
onClick={toggleItemM}
/>
<Body isOpen={isOpen}>
<Collapse isOpen={isOpen}>{item.content}</Collapse>
</Body>
</Container>
);
})}
</Wrapper>
);
};
In order to allow for multiple collapsible column, you can make use of an object instead of a single index
const Accordion = ({ list, icon}) => {
const [isActivePanel, setActivePanel] = useState({});
const toggleItem = index => {
setActivePanel(prevState => ({...prevState, [index]: !Boolean(prevState[index])}));
};
return (
<Wrapper>
{list.map((item, index) => {
const checkOpen = isActivePanel[index];
return (
<Container key={index}>
<Header
title={item.title}
icon={icon}
id={index}
onClick={toggleItem}
/>
<Body isOpen={checkOpen}>
<Collapse isOpen={checkOpen}>{item.content}</Collapse>
</Body>
</Container>
);
})}
</Wrapper>
);
};

Update list when redux store changes

I am trying to update the list when my redux store changes but for some odd reason it isn't. I have to manually refresh the page to see my changes. Here's the snippet of my List component and rowRenderer.
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={visibleRequest.length}
>
{({ onRowsRendered, registerChild }) => (
<AutoSizer>
{({ height, width }) => (
<List
ref={registerChild}
className="List"
height={height}
rowHeight={listRowHeight}
onRowsRendered={onRowsRendered}
rowCount={rowCount}
rowRenderer={this._rowRenderer}
width={width}
/>
)}
</AutoSizer>
)}
</InfiniteLoader>
_rowRenderer = ({ index, key, style }) => {
const { loadedRowsMap, selected } = this.state;
const row = this.getDatum(index);
let content;
if (loadedRowsMap[index] === STATUS_LOADED) {
content = row;
} else {
content = (
<div className="placeholder" style={{ width: _.random(100, 200) }} />
);
}
return (
<PendingChat
key={key}
content={content}
style={style}
row={row}
{...this.props}
/>
);
};
Yeah, I ran into the same problem. Its because the references to your objects don't change when you do
const row = this.getDatum(index);
let content;
if (loadedRowsMap[index] === STATUS_LOADED) {
content = row;
}
Take a look at immutability.

autoHeight with WindowScroller doesn't render full List

when i use autoHeight parameter on WindowScroller's child (List),
it doesn't render whole list of items (only few of them), but without autoHeight everything renders, but the rest of page is not scrolling.
Thank you, there is my code and screenshot:
const bars = this.props.bars
const rowRenderer = ({ index, key, style }) => {
const bar = bars.get(index)
return (
<div key={key} style={style}>
<BarListItem
key={key}
onClick={this.props.onBarClick}
{...this.props}
{...bar}
/>
</div>
)
}
return <WindowScroller>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
<AutoSizer disableHeight>
{({ width }) => (
<List
autoHeight
onScroll={onChildScroll}
ref="List"
height={height}
rowCount={bars.size}
rowRenderer={rowRenderer}
rowHeight={height/5.5}
width={width}
isScrolling={isScrolling}
// onChildScroll={onChildScroll}
scrollTop={scrollTop}
/>)}
</AutoSizer>
)}
</WindowScroller>
screenshot

Categories