react-virtualized Infinite scroller issues with dynamic height - reactjs

I am trying to mock Facebook feed like scroller in my application with react-virtualized. I am following the reference from here. I am trying to load two feeds at a time and after that loadMoreRows would be called to fetch the next two. I have hardcoded my feed size to 10 for testing purpose. It works well till 4th feed. Then I am not able to move after that smoothly. rowRenderer is triggering the numbers again and again which results in vibration effect on the screen. If I somehow move to the 10th feed and I scroll back, rowRenderer starts from 0 again. I assume it is due to varying height. Similar to the reference, I have used CellMeasurerCache and CellMeasurer to find the dynamic height and width and passing that to list.
class Scroller extends React.Component {
_cache = new CellMeasurerCache({ defaultHeight: 100, fixedWidth: true });
_resizeAllFlag = false;
_mostRecentWidth = 0;
constructor(props) {
super(props);
this.state = {
localCache: []
}
}
componentDidMount(){
this._loadData(0);
}
componentDidUpdate(prevProps, prevState) {
console.log(this._list);
if(this._resizeAllFlag){
this._resizeAllFlag = false;
this._cache.clearAll();
this._recomputeRowHeights();
} else if(this.state.localCache !== prevState.localCache) {
this._cache.clear(index, 0);
this._recomputeRowHeights(index);
}
}
._loadData = (offset, callback) => {
//Loads data from server and sets it in this.state.localCache
}
_recomputeRowHeights = (index) => {
if (this._list) {
console.log('Recomputing');
this._list.recomputeRowHeights(index);
}
}
_isRowLoaded = ({ index }) => {
return !!this.state.localCache[index];
}
_loadMoreRows = ({ startIndex, stopIndex }) => {
this._loadData(startIndex, (() => promiseResolver));
let promiseResolver;
return new Promise((resolve) => {
promiseResolver = resolve;
});
}
rowRenderer = ({ index, key, style, parent }) => {
const row = this.state.localCache[index];
let content;
if (row) {
content = (<Feed data={row}/>);
} else {
content = (<CustomLoader />);
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
{content}
</CellMeasurer>);
}
_setListRef = (ref) => {
this._list = ref;
this._registerList(ref);
};
_resizeAll = () => {
this._resizeAllFlag = false;
this._cache.clearAll();
if (this._list) {
this._list.recomputeRowHeights();
}
};
render() {
const { localCache } = this.state;
return (
<div className="flex_grow">
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={10}
>
{({ onRowsRendered, registerChild }) =>
<AutoSizer disableHeight>
{({ width, height }) => {
if (this._mostRecentWidth && this._mostRecentWidth !== width) {
this._resizeAllFlag = true;
setTimeout(this._resizeAll, 0);
}
this._mostRecentWidth = width;
this._registerList = registerChild;
return (
<List
deferredMeasurementCache={this._cache}
overscanRowCount={1}
ref={this._setListRef}
height={height}
onRowsRendered={onRowsRendered}
rowCount={10}
rowHeight={this._cache.rowHeight}
rowRenderer={this.rowRenderer}
width={width}
/>
)
}
}
</AutoSizer>}
</InfiniteLoader>
</div>
);
}
}
Update
I might have deleted style props in content that is being passed. As per #Adrien's suggestion, I added it. My issues were not solved after adding style props.
rowRenderer = ({ index, key, style, parent }) => {
const row = this.state.localCache[index];
let content;
if (row) {
content = (<Feed style={style} data={row}/>);
} else {
content = (<CustomLoader style={style}/>);
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
{content}
</CellMeasurer>);
}
And my Feed component
class Feed extends React.Component {
constructor(props) {
super(props);
}
render() {
const { style } = this.props;
return (
<div className="flex_grow" style={style}>
{/* Feed related JSX */}
</div>
);
}
}
My components seems to be overlapping. What could have been wrong?
AnswerGist:
https://gist.github.com/beb4/cc91f4e9b8982d172613cff248090769

You forgot to pass the rowRenderer style in your content component. According to the docs, this style is mandatory to position your row.
Corrected rowRenderer
rowRenderer = ({ index, key, style, parent }) => {
const row = this.state.localCache[index];
let content;
if (row) {
content = (<Feed data={row} style={style} />); // <== HERE
} else {
content = (<CustomLoader style={style} />); // <== AND HERE
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
{content}
</CellMeasurer>
);
}

Related

How do I manage my array of children components' states?

I'm new to react, so forgive me. I'm having a problem understanding states, specifically those of children.
Purpose: I'm trying to create a form that a user can append more and more components -- in this case, images.
What happens: User appends 2 or more images. User tries to upload an image with UploadButton component, but both the images are the same. I believe this has to do with both appended children sharing the same state.
Question: How do I give each appended child its own image without affecting the other appended children?
class Page extends Component
constructor (props) {
super(props);
this.state = {
id: '',
numChildren: 0,
images: [],
}
this.onAddChild = this.onAddChild.bind(this);
}
showModal() {
this.setState({
numChildren: 0,
images: [],
});
}
renderModal()
const children = [];
//Here's my array of child components
for(var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent key={i} />);
}
return (
<ReactModal>
<this.ParentComponent addChild={this.onAddChild}>
{children}
</this.ParentComponent>
</ReactModal>
)
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
})
}
ParentComponent = (props) => (
<div>
{props.children}
<Button onClick={props.addChild}>Add Item</Button>
</div>
);
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={this.state.images}
onUploadComplete={uri => this.setState({images: uri})}
/>
</div>
);
}
Here's the code for UploadButton:
import React, { Component } from 'react';
import uuid from 'uuid';
import firebase from '../config/firebase';
class UploadButton extends Component {
constructor(props) {
super(props);
this.state = {
isUploading: false
}
}
handleClick() {
const input = document.createElement("INPUT");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/gif, image/jpeg, image/png");
input.addEventListener("change", ({target: {files: [file]}}) => this.uploadFile(file));
input.click();
}
uploadFile(file) {
console.log('F', file);
const id = uuid.v4();
this.setState({ isUploading: true })
const metadata = {
contentType: file.type
};
firebase.storage()
.ref('friends')
.child(id)
.put(file, metadata)
.then(({ downloadURL }) => {
this.setState({ isUploading: false })
console.log('Uploaded', downloadURL);
this.props.onUploadComplete(downloadURL);
})
.catch(e => this.setState({ isUploading: false }));
}
render() {
const {
props: {
value,
style = {},
className = "image-upload-button",
},
state: {
isUploading
}
} = this;
return (
<div
onClick={() => this.handleClick()}
className={className}
style={{
...style,
backgroundImage: `url("${this.props.value}")`,
}}>
{isUploading ? "UPLOADING..." : !value ? 'No image' : ''}
</div>
);
}
}
export default UploadButton;
I tried to exclude all unnecessary code not pertaining to my problem, but please, let me know if I need to show more.
EDIT: This is my attempt, it doesn't work:
//altered my children array to include a new prop
renderModal() {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent imageSelect={this.onImageSelect} key={i} />);
}
//...
};
//my attempt to assign value and pass selected image back to images array
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={uri => this.props.onImageSelect(uri)} //my greenness is really apparent here
onUploadComplete={uri => this.setState({images: uri})}
/>
//...
</div>
);
//added this function to the class
onImageSelect(uri) {
var el = this.state.images.concat(uri);
this.setState({
images: el
})
}
I know I'm not accessing the child prop correctly. This is the most complexity I've dealt with so far. Thanks for your time.
When you write this.state in Child / Parent component, you are actually accessing the state of Page. Now, I would recommend that you pass in the index of the child to the Child like so
children.push(<this.ChildComponent key={i} index={i}/>)
so that each children deals with only its own image like so
ChildComponent = ({index}) => (
<div>
<UploadButton
storage="menus"
value={this.state.images[index]}
onUploadComplete={uri => {
let images = this.state.images.slice()
images[index] = uri
this.setState({images})
}}
/>
</div>
);

React native section list not re-rendering when item changes

I have a sectionList in my react native project. it does not re-render if item changes.
My code:
test.js
class Test extends React.Component {
started = false;
causeData=[];
showLess=false;
items = [];
_start = () => {
const { ws } = this.props;
this.showLess = false;
if (ws.causes.length) {
this.causeData = {
title: Language.causes,
key: "cause",
data: []
};
ws.causes.forEach(cause => {
let causeDetails = {
key: "cause_" + cause.id,
name: "",
value: cause.name,
sortIndex: cause.sortIndex,
progress: cause.progress
};
this.causeData.data.push(causeDetails);
if (this.causeData.data.length > 4) {
this.causeData.data = this.causeData.data.slice(0, 4);
}
});
this.items.push(this.causeData);
console.log("causeData", this.causeData);
}
}
}
_renderItem = ({ item }) => {
return (
<View>
<Text key={item.key} style={styles.text}>{`${item.name} ${
item.value
}`}</Text>
</View>
);
};
_renderSectionHeader = ({ section }) => {
const { ws } = this.props;
const showMore = ws.causes.length > 0 && !this.showLess;
return (
<View style={styles.sectionHeader}>
<Text key={section.key} style={styles.header}>
{section.title}
</Text>
{showMore && (
<Button
onPress={this._afterCauseAnswered}
title={Language.showMore}
data={this.items}
accessibilityLabel={Language.causeDoneAccessibility}
/>
)}
</View>
);
};
_keyExtractor = (item, index) => item.key;
_afterCauseAnswered = () => {
const { stepDone, ws } = this.props;
this.causeData.data = { ...ws.causes };
stepDone("showMoreAnswered");
this.showLess = true;
};
render = () => {
if (!this.started) {
this.started = true;
this._start();
}
return (
<View style={styles.container}>
<SectionList
sections={this.items}
extraData={this.items}
renderItem={this._renderItem}
renderSectionHeader={this._renderSectionHeader}
keyExtractor={this._keyExtractor}
/>
</View>
);
};
}
in my section list section header contain a button called showMore. At initial rendering it will only show 5 items, while clicking showMore it should display all List. This is my functionality. but while clicking showMore button it will not show entire list only shows 5 items that means the sectionList does not getting re-render. How to resolve this? i am new to react native. Any idea what am I missing ? Any help would be greatly appreciated!
Keep items and showLess in a state and after pressing Button call setState with the new values. It will rerender the SectionList. Also, if you want to display multiple items with a displayed list, you need to move showLess to the item element so each item knows how to display it.
You just need to rerender your screen using state and it's done
this.setState({})
Your SectionList should always read from state ... as it should be your single source of truth
and here's how:
class YourComponent extends React.Component {
state = {
items: [],
};
// This will be called after your action is executed,
// and your component is about to receive a new set of causes...
componentWillReceiveProps(nextProps) {
const {
ws: { causes: nextCauses },
} = nextProps;
if (newCauses) {
// let items = ...
// update yout items here
this.setState({ items });
}
}
}

React ref assignment inconsistencies when using a mapping function?

I'm setting up a virtualized scrolling component in React, and using refs with a recycled observer to notify the app when to prepare another batch of data. Inside my Grid component, I map over the current batch of data and assign a ref to a sentinel div, except that ref returns null in componentDidMount(). I don't understand why since componentDidMount fires after render executes, so the reference should be available.
The only workaround to this I've found is using this janky solution in my componentDidMount: setTimeout(() => this.observer.observe(this.targetRef.current), 0);.
import React, { Component, createRef } from "react";
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
batch: []
};
this.observer = null;
this.targetRef = null;
this.lastRowFirstVisible =
props.rowCount * props.columnCount - props.columnCount;
this.config = {
rootMargin: "0px",
threshold: 1
};
this.setTargetRef = element => {
this.targetRef = element;
};
}
componentDidMount() {
const { startIndex, numberToDisplay } = this.props;
this.setBatch(startIndex, numberToDisplay);
this.observer = new IntersectionObserver(function(entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(entry);
// self.unobserve(entry.target);
}
});
}, this.config);
setTimeout(() => this.observer.observe(this.targetRef), 0);
}
setBatch = (startIndex, numberToDisplay) => {
const batch = this.getBatch(startIndex, numberToDisplay);
this.setState({ batch });
};
getBatch = (startIndex, numberToDisplay) => {
const { data } = this.props;
return data.slice(startIndex, numberToDisplay);
};
// TO DO
updateObserver = () => {
this.observer.observe(this.targetRef.current);
};
render() {
const { lastRowFirstVisible } = this;
const { batch } = this.state;
const { elementWidth, elementHeight } = this.props;
console.log(lastRowFirstVisible);
return (
<>
{batch.map((element, localIndex) => {
const { index } = element;
console.log(localIndex === lastRowFirstVisible);
return localIndex === lastRowFirstVisible ? (
<div
id={index}
key={index}
style={{ width: elementWidth, height: elementHeight }}
className="card"
ref={this.setTargetRef}
>
{this.props.renderRow(element)}
</div>
) : (
<div
key={index}
style={{ width: elementWidth, height: elementHeight }}
className="card"
>
{this.props.renderRow(element)}
</div>
);
})}
</>
);
}
}
export default Grid;
Expected results: render function finishes executing, assigns DOM node to this.targetRef for use in componentDidMount()
Actual results: this.targetRef is still null in componentDidMount()

React container with render logic?

I have a container, which renders 3 components:
1. list of items
2. new item modal
3. edit item modal
In order to control the whole container functions, I need to call the list of items with column list. Is it ok that all will be inside the container?
Is it ok to render modal within the container? (The modal contains the 2 and 3 components)
class Items extends React.Component {
constructor(props) {
super(props)
this.state = {
modal: false
}
this.columns = [
...
]
this.closeModal = this.closeModal.bind(this)
}
openModal(type, item) {
this.setState({
modal: {
type,
item: item && item.toJS()
}
})
}
closeModal() {
this.setState({modal: false})
}
renderModal() {
const {item, type} = this.state.modal;
return (
<Modal onClose={this.closeModal}>
{type == modalTypes.NEW_ITEM &&
<ItemForm onCancel={this.closeModal}
onSubmit={...}/>}
{type == modalTypes.REMOVE_ITEM &&
<ConfirmationDialog text="Are you sure you want to remove?"
onSubmit={...} onCancel={this.closeModal}/>}
{type == modalTypes.EDIT_ITEM &&
<ItemForm onCancel={this.closeModal}
onSubmit={...}/>}
</Modal>
)
}
render() {
const {visibleItems, display_type} = this.props;
return (
<div>
<div className="_header_container">
<Header title="Items"/>
<div className="actions">
<Search />
<DisplayToggle />
<Button size="sm" color="primary"
onClick={() => ...}
</div>
</div>
{display_type == displayType.GRID &&
<Grid items={visibleItems} columns={this.columns}/>}
{display_type == displayType.TILE &&
<TileView items={visibleItems} titleKey="name" linkKey="url"/>}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
remove: (item) => dispatch(remove(item)),
edit: (item, ...) => dispatch(edit(item, ...)),
create: (name, val) => dispatch(create(name, url)),
}
}
const mapStateToProps = (state) => {
return {
visibleItems: filterItems(state.items, state.search),
display_type: state.display_type
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Items)
Thanks

react-virtualized getting error - TypeError: list.get is not a function

Getting error - TypeError: list.get is not a function
My Parent component :
import TableExample from './TableExample';
// Map Redux state to component props
function mapStateToProps(state) {
return {
dirList: state.filelistReducer.dirList,
showFileViewer: state.sidenavReducer.showFileViewer,
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
onDirClick: (path) => { dispatch(actions.fetchFileListAction(path, dispatch)); },
};
}
/**
*
*
* #class FileViewerComponent
* #extends {Component}
*/
class FileListViewComponent extends Component {
constructor(props) {
super(props);
this.state = { width: 0 }
}
render() {
return (
<div>
<div className="col-md-10">
<div className="contentviewdivider" style={{ width: (this.state.width) + 'px' }}></div>
<div className="mainContainer" style={{ width: (this.state.width) + 'px' }}>
<div className="tilerow">
{this.renderList()}
</div>
</div>
</div>
</div>
);
}
renderList() {
return (
<div>
<div className={this.props.showFileViewer}>
<div className='row'>
<TableExample filelist={this.props.dirList} />
</div>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FileListViewComponent);
Now in TableExample.jsx
/** #flow */
import Immutable from 'immutable';
import { List } from 'immutable';
import React, { PropTypes, PureComponent } from 'react';
import ReactDom from 'react-dom';
import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox'
import { LabeledInput, InputRow } from '../demo/LabeledInput'
import {AutoSizer,Column, Table, SortDirection, SortIndicator} from 'react-virtualized'
import styles from '../../../styles/css/components/tableexample.css'
//import { generateRandomList } from './utils'
export default class TableExample extends PureComponent {
// static contextTypes = {
// list: PropTypes.instanceOf(Immutable.List).isRequired
// };
constructor (props) {
super(props)
this.state = {
disableHeader: false,
headerHeight: 30,
height: 270,
hideIndexRow: false,
overscanRowCount: 10,
rowHeight: 40,
rowCount: 1000,
scrollToIndex: undefined,
sortBy: 'index',
sortDirection: SortDirection.ASC,
useDynamicRowHeight: false
}
this._getRowHeight = this._getRowHeight.bind(this)
this._headerRenderer = this._headerRenderer.bind(this)
this._noRowsRenderer = this._noRowsRenderer.bind(this)
this._onRowCountChange = this._onRowCountChange.bind(this)
this._onScrollToRowChange = this._onScrollToRowChange.bind(this)
this._rowClassName = this._rowClassName.bind(this)
this._sort = this._sort.bind(this)
}
render () {
const {
disableHeader,
headerHeight,
height,
hideIndexRow,
overscanRowCount,
rowHeight,
rowCount,
scrollToIndex,
sortBy,
sortDirection,
useDynamicRowHeight
} = this.state
console.log('render of tableexample');
console.log(this.props);
const list = this.props.filelist;
const sortedList = this._isSortEnabled()
? list
.sortBy(item => item[sortBy])
.update(list =>
sortDirection === SortDirection.DESC
? list.reverse()
: list
)
: list
const rowGetter = ({ index }) => this._getDatum(sortedList, index)
return (
<ContentBox>
<div className="table table-striped">
<AutoSizer disableHeight>
{({ width }) => (
<Table
ref='Table'
disableHeader={disableHeader}
headerClassName={styles.headerColumn}
headerHeight={headerHeight}
height={height}
noRowsRenderer={this._noRowsRenderer}
overscanRowCount={overscanRowCount}
rowClassName={this._rowClassName}
rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight}
rowGetter={rowGetter}
rowCount={rowCount}
scrollToIndex={scrollToIndex}
sort={this._sort}
sortBy={sortBy}
sortDirection={sortDirection}
width={width}
>
{!hideIndexRow &&
<Column
label='Index'
cellDataGetter={
({ columnData, dataKey, rowData }) => rowData.index
}
dataKey='index'
disableSort={!this._isSortEnabled()}
width={60}
/>
}
<Column
dataKey='name'
disableSort={!this._isSortEnabled()}
headerRenderer={this._headerRenderer}
width={90}
/>
<Column
width={210}
disableSort
label='The description label is really long so that it will be truncated'
dataKey='random'
className={styles.exampleColumn}
cellRenderer={
({ cellData, columnData, dataKey, rowData, rowIndex }) => cellData
}
flexGrow={1}
/>
</Table>
)}
</AutoSizer>
</div>
</ContentBox>
)
}
_getDatum (list, index) {
// Getting error here : TypeError: list.get is not a function[Learn More]
return list.get(index % list.size)
}
_getRowHeight ({ index }) {
const list = this.props.filelist;
return this._getDatum(list, index).size
}
_headerRenderer ({
columnData,
dataKey,
disableSort,
label,
sortBy,
sortDirection
}) {
return (
<div>
Full Name
{sortBy === dataKey &&
<SortIndicator sortDirection={sortDirection} />
}
</div>
)
}
_isSortEnabled () {
const list= this.props.filelist;
const { rowCount } = this.state
return rowCount <= list.size
}
_noRowsRenderer () {
return (
<div className={styles.noRows}>
No rows
</div>
)
}
_onRowCountChange (event) {
const rowCount = parseInt(event.target.value, 10) || 0
this.setState({ rowCount })
}
_onScrollToRowChange (event) {
const { rowCount } = this.state
let scrollToIndex = Math.min(rowCount - 1, parseInt(event.target.value, 10))
if (isNaN(scrollToIndex)) {
scrollToIndex = undefined
}
this.setState({ scrollToIndex })
}
_rowClassName ({ index }) {
if (index < 0) {
return styles.headerRow
} else {
return index % 2 === 0 ? styles.evenRow : styles.oddRow
}
}
_sort ({ sortBy, sortDirection }) {
this.setState({ sortBy, sortDirection })
}
_updateUseDynamicRowHeight (value) {
this.setState({
useDynamicRowHeight: value
})
}
}
I am getting error inside -
_getDatum (list, index) {
// Getting error here : TypeError: list.get is not a function[Learn More]
return list.get(index % list.size)
}
I am not using immutable list, instead using my own object. If I do
console.log(this.props.filelist) // Object { filelist: Array[12] }
0:Object
absPath:"/home/testmaximumcharactersforfolders123"
created:1490586030000
filename:"testmaximumcharactersforfolders123"
hasChildren:true
isHidden:false
isReadable:true
isWritable:false
modified:1490586030000
owner:"root"
size:4096
type:"FILE_DIR"
I have one more question, as you can see my object structure above what should be my datakey ? As per my understanding using datakey we are referring key name in our object . so should it be datakey="filename" if I want to show filename data in the column ? And is it ok if my object contains other key value pair which I am not going to show in the table ?
Please help...
Screenshot of overlapping :

Resources