React Virtualized Masonry does not resize with the browser - reactjs

I am trying to build a feed (a Pinterest-like feed to put it straight). I am using react-virtualized Masonry component.
You can see how the items rearrange and the component is correctly resized when the browser window resizes in this screen recording.
However, mine has a strange behavior as you can see in this screen recording.
Here's the relevant excerpt of my code:
export default class Feed extends Component <PropTypes, State> {
static readonly defaultProps = {
enableInfiniteScroll: false,
chunkSize: 9,
};
private _windowScroller: WindowScroller;
private _masonry: Masonry;
private _columnCount: number;
private _cache: CellMeasurerCache;
private _cellPositioner: Positioner;
constructor(props: PropTypes) {
super(props);
// ...
this._columnCount = 3;
this._cache = new CellMeasurerCache({
defaultWidth: COLUMN_WIDTH,
defaultHeight: 400,
fixedWidth: true,
});
this._cellPositioner = createMasonryCellPositioner({
cellMeasurerCache: this._cache,
columnCount: this._columnCount,
columnWidth: COLUMN_WIDTH,
spacer: GUTTER,
});
}
onResize({width}: Size) {
this._cache.clearAll();
this.calculateColumnCount(width);
this.resetCellPositioner();
this._masonry.recomputeCellPositions();
}
cellRenderer(cellProps: MasonryCellProps) {
const {items} = this.state;
const listing = items.get(cellProps.index);
return (
<CellMeasurer
cache={this._cache}
index={cellProps.index}
key={cellProps.key}
parent={cellProps.parent}
>
<div style={cellProps.style}>
<ListingCard company={listing} />
</div>
</CellMeasurer>
);
}
calculateColumnCount(width: number) {
this._columnCount = Math.floor((width + GUTTER) / (COLUMN_WIDTH + GUTTER));
}
resetCellPositioner() {
this._cellPositioner.reset({
columnCount: this._columnCount,
columnWidth: COLUMN_WIDTH,
spacer: GUTTER,
});
}
render() {
const {items, isLoading, hasMore} = this.state;
return (
<div className={Styles['listings-feed']}>
<WindowScroller scrollElement={window} ref={this.setRef}>
{({height, isScrolling, onChildScroll, scrollTop, registerChild}) => (
<div className={Styles.windowScrollerContainer}>
<AutoSizer disableHeight onResize={this.onResize}>
{({width}) => (
<div ref={registerChild as any}>
<Masonry
cellCount={items.size}
cellMeasurerCache={this._cache}
cellPositioner={this._cellPositioner}
cellRenderer={this.cellRenderer}
height={height}
width={width}
autoHeight
ref={(r: Masonry) => this._masonry = r}
/>
</div>
)}
</AutoSizer>
</div>
)}
</WindowScroller>
</div>
);
}
}

After testing with different parameters and tweaks, I found out is was not rendering all of the items because they were technically out of range (not in the user's view). They were not out of view when scrolling, it is just that the <Masonry /> component only updates on property changes.
Since I am using a <WindowScroller /> component, I found out it offers a scrollTop variable for the children function so I passed this directly to the Masonry component:
<WindowScroller scrollElement={window} ref={this.setRef}>
{({height, isScrolling, onChildScroll, scrollTop, registerChild}) => (
<div className={Styles.windowScrollerContainer}>
<AutoSizer disableHeight onResize={this.onResize}>
{({width}) => (
<div ref={registerChild as any}>
<Masonry
cellCount={items.size}
cellMeasurerCache={this._cache}
cellPositioner={this._cellPositioner}
cellRenderer={this.cellRenderer}
height={height}
width={width}
autoHeight
ref={(r: Masonry) => this._masonry = r}
scrollTop={scrollTop}
/>
</div>
)}
</AutoSizer>
</div>
)}
</WindowScroller>

Related

Cannot read property 'slideHandler' of null in react-slick

I have a component that uses two sliders in combination(a total of 4). One combination is for horizontal carousel and another for vertical. This decision is based upon the screen size.
When it is rendered in smaller devices it is working fine but on larger screens, it renders but as soon as I click on buttons for moving the slides it crashes and presents me with the following error.
I am using react-slick for the carousel.
Here's the component code:
class Carousel extends Component {
constructor(props) {
super(props);
this.state = {
imageSlider: null,
contentSlider: null,
isMobile: true
};
this.getHorizontalSlider = this.getHorizontalSlider.bind(this);
this.getVerticalSlider = this.getVerticalSlider.bind(this);
}
componentDidMount() {
const isMobile = window.screen.width > 768 ? false : true;
this.setState({
imageSlider: this.slider1,
contentSlider: this.slider2,
isMobile: isMobile
});
}
getHorizontalSlider() {
return (
<div className={this.props.className}>
<Slider
asNavFor={this.state.contentSlider}
ref={slider => (this.slider1 = slider)}
className={styles.horizontalImageSlider}
focusOnSelect={false}
arrows={false}
dots={true}
autoplay={true}
autoplaySpeed={7000}
>
{
data.map((item, index) => (
<div key={index}>
<img src={item.imageUrl} className={`${styles.carouselImage} img-fluid mx-auto`} alt="" />
</div>
))
}
</Slider>
<Slider
asNavFor={this.state.imageSlider}
ref={slider => (this.slider2 = slider)}
focusOnSelect={false}
arrows={false}
>
{
data.map((item, index, arr) => (
<div key={index} className='text-center text-secondary'>
<h2 className="text-primary fw-bold mb-4">{item.heading}</h2>
<p className='lead px-2'>{item.description}</p>
</div>
))
}
</Slider>
</div>
)
}
getVerticalSlider() {
return (
<div className={`row ${this.props.className}`}>
<div className="col-4">
<Slider
asNavFor={this.state.contentSlider}
ref={slider => (this.slider1 = slider)}
vertical={true}
arrows={false}
slidesToScroll={1}
verticalSwiping={false}
focusOnSelect={false}
slidesToShow={1}
>
{
data.map((item, index) => (
<div key={index}>
<img src={item.imageUrl} className={`${styles.carouselImage} img-fluid mx-auto`} alt="" />
</div>
))
}
</Slider>
</div>
<div className="col-8">
<div className='d-flex justify-content-center align-items-center h-100'>
<Slider
className={styles.verticalContentSlider}
asNavFor={this.state.imageSlider}
ref={slider => (this.slider2 = slider)}
slidesToScroll={1}
slidesToShow={1}
swipeToSlide={true}
focusOnSelect={true}
vertical={true}
adaptiveHeight={true}
nextArrow={<CustomArrow />}
prevArrow={<CustomArrow />}
>
{
data.map((item, index, arr) => (
<div key={index}>
{getSlideContent(item, index, arr)}
</div>
))
}
</Slider>
</div>
</div>
</div>
)
}
render() {
if (this.state.isMobile === true) {
return this.getHorizontalSlider();
}
return this.getVerticalSlider();
}
}
I know it's not the best code out, but I want to make this work first and then refactor it.
Thanks for reading this, much appreciated.
It looks like your setState() will only be executed once, after the component mounts with the mobile defaults. That means that when your component re-renders to show the vertical sliders, imageSlider and contentSlider still point to the mobile, horizontal sliders. This would cause react-slick to throw an error when trying to resolve your asNavFor settings.
One way to avoid this might be to set your default mobile state in the constructor instead of componentDidMount, so that only one set of components (horizontal or vertical) will ever be rendered.
constructor(props) {
super(props);
this.state = {
imageSlider: null,
contentSlider: null,
isMobile: window.screen.width <= 768
};
...
}

React-virtualized list running below 60fps. How to optimize?

I am trying to create a post feed like the one instagram has (on the main page).
I'm using Infinite-loader for fetching, Window-scroller for using the window as the scroll, auto-sizer for sizing the list how i want and CellMeasurer for measuring the 'post component' once after the image has been loaded.
Here is code for the list component:
class PostsPartial extends React.PureComponent<IProps>{
state: IPostsPartialState = { posts: [], hasMorePosts: true }
private cache: CellMeasurerCache;
private get rowCount(): number {
return this.state.hasMorePosts ? this.state.posts.length + 1 : this.state.posts.length;
}
constructor(props: IProps) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 1000
});
this.renderRow = this.renderRow.bind(this);
}
private fetchPosts = ({ startIndex, stopIndex }: { startIndex: number, stopIndex: number }) => {
return getNewPostsChunk(startIndex, stopIndex - startIndex, this.props.token).then((res: IPostsChunkResponse) => {
if (res.success) {
if (res.posts.length === 0) {
// no more posts
this.setState({ hasMorePosts: false })
}
else {
let newPosts = [...this.state.posts, ...res.posts];
this.setState({ posts: newPosts })
}
}
else {
// internal error
}
})
};
private renderRow({ index, key, parent, style }: any) {
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure, registerChild }: any) => (
<div className={styles.paddingContainer} ref={registerChild} style={style}>
<Post
isLoaded={this.isRowLoaded({index})}
measure={measure}
post={this.state.posts[index]}
/>
</div>
)}
</CellMeasurer>
);
}
private isRowLoaded = ({ index }: { index: number }) => {
return !!this.state.posts[index];
};
public render() {
return (
<div className={styles.mainContainer}>
<InfiniteLoader
isRowLoaded={this.isRowLoaded}
loadMoreRows={this.fetchPosts}
rowCount={this.rowCount}
>
{({ onRowsRendered, registerChild }: InfiniteLoaderChildProps) => (
<WindowScroller>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
<AutoSizer disableHeight>
{
({ width }: any) => (
<List
ref={registerChild}
onRowsRendered={onRowsRendered}
autoHeight
width={width}
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow}
rowCount={this.rowCount}
overscanRowCount={10}
/>
)
}
</AutoSizer>
)}
</WindowScroller>
)}
</InfiniteLoader>
</div>
);
}
and here is code for the post component:
const Post:React.FC<IProps> = (props:IProps) => {
if(props.post && props.isLoaded)
return (
<div className={styles.container}>
<Segment className={styles.profileSegmentInternal} attached='top'>
<Image className={styles.verySmallImg} circular size='tiny' src={`${settings.BASE_URL}/feed/photo/user/${props.post.creator}`}></Image>
<Link to={`/profile/${props.post.creator}`}>
<Header size='small' className={styles.headerName} as='span'>{props.post.creator}</Header>
</Link>
</Segment>
<div className={styles.imageContainer}>
<Image onLoad={props.measure} src={`${settings.BASE_URL}/feed/photo/post/${props.post._id}`} className={styles.image}></Image>
</div>
<Segment className={styles.bottomSegment} attached='bottom'>
<>
<Menu className={styles.postMenu}>
<Item className='left'>
<Icon className={styles.iconBtn} size='big' name='heart outline'></Icon>
<Icon className={styles.iconBtn} size='big' name='comment outline'></Icon>
<Icon className={styles.iconBtn} size='big' name='paper plane outline'></Icon>
</Item>
<Item className='right'>
<Icon className={styles.iconBtn} size='big' name='bookmark outline'></Icon>
</Item>
</Menu>
</>
<Header className={styles.likes} size='tiny'>{props.post.likesCount} likes</Header>
<Header className={styles.description} size='tiny'>
<Header size='tiny' className={styles.commentUsername} as='span'>{props.post.creator}</Header>
<Header className={styles.commentText} as='span' size='tiny'> {props.post.description}</Header>
</Header>
<Link to='#'>
<Header className={styles.viewAllComments} size='tiny' disabled>View all comments</Header>
</Link>
{
//backend will return the first 3-4 messeges only
// props.post.messeges.map((messege,index) => (
// ))
}
<Form className={styles.commentForm}>
<Form.Field className={styles.commentField}>
<Form.Input
className={styles.commentInput}
placeholder='Adding comment ...'
>
</Form.Input>
<Button className={styles.commentSubmit} size='medium' primary>Comment</Button>
</Form.Field>
</Form>
</Segment>
</div>
)
else
return (
<p>loading</p>
)
Even if I remove everything from the post component and leave only the image, it still won't run with more then 45-50fps sometimes going under 40fps too.
Can I optimize my approach in any way or am I doing something wrong?
Should I provide anything else that might be helpful?
Thank you in advance!
So I fixed my problem by resizing the image when uploading it (in the backend using sharp).
This way the fetching is faster witch makes the (just in time) measure of a post component way faster as less data needs to be loaded on mount, and html+css doesn't have to resize the high image into a smaller container.
Sounds silly, but I didn't think of this being the issue and instead focused on my infinite scrolling implementation :D
Ya live and ya learn
EDIT:
I forgot to mention, a small change I did when setting the image src.
Instead of making an express route that retrieves the src, I can just use it while receiving the post info. The file source won't be printed with console.log or whatever, but it is there and can be used like so:
<Image className={styles.image} onLoad={props.measure} src={`data:${props.post.source.contentType};base64,${Buffer.from(props.post.source.data).toString('base64')}`} />

Logic for Three-dot more options menu for web applications in reactjs

I am new to ReactJs and I'm developing a social media web application. Here I have template where I have to implement a Three-dot more options menu. I tried using Bootstrap menu and react Bootstrap component menu. Both didn't work for me. What is the best way to implement this feature without using a library?
I did till toggling the menu. But on click, all the menus toggle altogether. I am not able do the toggling individually.
Here's the piece of code I did:
post.jsx
class UserPost extends Component {
state = {
overFlowMenuActive: false
};
toggleOverflowMenu = () => {
this.setState((prevState) => ({ overFlowMenuActive:
!prevState.overFlowMenuActive }));
};
closeOverflowMenu = () => {
this.setState({ overFlowMenuActive: false });
};
render() {
return (
<React.Fragment>
{this.props.posts.map((post, index) =>(
<div>
<div tabIndex='0' onBlur={this.closeOverflowMenu}>
<img src={require('../../assets/images/more.svg')} alt='' onClick={this.toggleOverflowMenu}/>
</div>
<MoreBtn options={this.state.options} overFlowMenuActive={this.state.overFlowMenuActive} />
</div>
))}
</React.Fragment>
);
}
MoreBtn.jsx
<div className={`${classes['popup-more']} ${this.props.overFlowMenuActive
? classes.expand
: classes.collapse}`}>
{this.props.options.map((option, index) => (
<div key={index}>
<img src={option.url} alt='' />
<p>{option.name}</p>
</div>
))}
</div>
You are maintaining only a single state for all UserPosts
To have each of these toggle seperately, these states should be moved into the component.
class SinglePost extends Component {
state = {
overFlowMenuActive: false
};
toggleOverflowMenu = () => {
this.setState((prevState) => ({ overFlowMenuActive:
!prevState.overFlowMenuActive }));
};
closeOverflowMenu = () => {
this.setState({ overFlowMenuActive: false });
};
render() {
return (
<div>
<div tabIndex='0' onBlur={this.closeOverflowMenu}>
<img src={require('../../assets/images/more.svg')} alt='' onClick={this.toggleOverflowMenu}/>
</div>
<MoreBtn options={this.state.options} overFlowMenuActive={this.state.overFlowMenuActive} />
</div>
);
}
class UserPost extends Component {
render() {
return (
<React.Fragment>
{this.props.posts.map((post, index) =>(
<SinglePost post={post} />
))}
</React.Fragment>
);
}
This way, the button for only one component is toggled at a time

Cannot read property 'tabsDivIframe' of undefined - React

I am creating an application with tabs and divs to show the iframes or divs associated with the tabs. I have a navigation menu that works perfectly, when you click on one of the menu items you create a new tab and at the same time you should create a div / iframe (as applicable). The creation of the div is failing in my DivAndIframe class, it gives this error Can not read property 'tabsDivIframe' of undefined when I try to paint <DivAndIframe tabsDivIframe {this.props.divIframe.tabsDivIframe} />. It does not make sense because in my class App is an array with content that does not throw any errors.
class App extends Component {
constructor(props, context){
super(props, context);
["openTabs"].forEach((method) => {
this[method] = this[method].bind(this);
});
this.state = {
tabs:{
tabsLi: [],
},
divIframe:{
tabsDivIframe: [],
},
showtabs: true,
}
}
openTabs(e, url, iframe, trdtitle){
e.preventDefault();
//Change the state
this.setState({
showtabs: false,
})
//Creating tabs + iframe/div
if (this.state.tabs.tabsLi.includes(trdtitle) === false){
this.setState({
tabs: { tabsLi:[...new Set(this.state.tabs.tabsLi),trdtitle].filter(function(el) { return el; })},
divIframe: { tabsDivIframe:[...new Set(this.state.divIframe.tabsDivIframe),url].filter(function(el) { return el; })},
}, () => {
//this.state.divIframe.tabsDivIframe is an array
console.log(this.state.tabs.tabsLi);console.log(this.state.divIframe.tabsDivIframe)
})
}
}
render(){
return (
<>
<section className='section'>
<Tabs
navigation={this.state.navigation}
textvalue={this.state.textvalue}
showtabs={this.state.showtabs}
tabs={this.state.tabs}
tabsLi={this.state.tabs.tabsLi}
tabsDivIframe={this.state.divIframe.tabsDivIframe}
openTabs={this.openTabs}
removeTab={this.removeTab}
/>
</section>
</>
)
}
}
class Tabs extends Component {
render(){
return(
<div id="content-tabs" className="tabs">
{( this.props.showtabs)
? (
<>
<div className="waiting-leads">
<p>Parece que todavía no hay ningún lead...</p>
<h3>¡Ánimo, ya llega!</h3>
<img src={imgDinosaurio} alt="Dinosaurio"></img>
</div>
</>
) : (
<>
<ul id="resizable" className="content" >
<LiTabs
tabsLi={this.props.tabs.tabsLi}
removeTab={this.props.removeTab}
/>
</ul>
<DivAndIframe
tabsDivIframe={this.props.divIframe.tabsDivIframe}
/>
</>
)}
</div>
);
}
}
class LiTabs extends Component{
render(){
return(
<>
{this.props.tabsLi.map((value, index) =>
<li key={index}>
<span>{value}</span>
</li>
)}
</>
);
}
}
class DivAndIframe extends Component{
render(){
return(
<>
{this.props.tabsDivIframe.map((url, index) =>
<div key={index}>
<span>Test {url}</span>
</div>
)}
</>
);
}
}
I do not understand why DivAndIframe does not work when it is exactly the same as LiTabs
I think you have a typo.
When rendering Tabs, in App, you pass the props:
<Tabs
navigation={this.state.navigation}
textvalue={this.state.textvalue}
showtabs={this.state.showtabs}
tabs={this.state.tabs}
tabsLi={this.state.tabs.tabsLi}
tabsDivIframe={this.state.divIframe.tabsDivIframe}
openTabs={this.openTabs}
removeTab={this.removeTab}
/>
And inside Tabs you have:
<DivAndIframe
tabsDivIframe={this.props.divIframe.tabsDivIframe}
/>
You aren't passing divIframe to Tabs and that is why you are getting Can not read property 'tabsDivIframe' of undefined. this.props.divIframe is undefined.
Maybe it should be other name?
Like this.props.tabsDivIframe ?

How to pass ref of component up three levels for scroll event?

I am using the <InfiniteScroll/> component in my code like this:
<div style="height:700px;overflow:auto;" ref={(ref) => this.scrollParentRef = ref}>
<div>
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true || false}
loader={<div className="loader" key={0}>Loading ...</div>}
useWindow={false}
getScrollParent={() => this.scrollParentRef}
>
{items}
</InfiniteScroll>
</div>
</div>
I need to pass the from ref = {(ref) => this.scrollParentRef = ref; } to the component's grandparent, however my code does not work as expected. Here is my full component source code:
class AppEmpty extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.props.setReferences(this.scrollParentRef);
}
render() {
const { children } = this.props;
const isActive = this.props.isActive;
return (
<div className="App">
<NavBar/>
<MenuRight/>
<div id="content"
className={isActive ? "content_enable": ""}
style={this.props.menuRight.styleContent}
ref={ (ref) => this.scrollParentRef = ref }
>
<FlashMessages/>
{children}
</div>
</div>
);
}
}
AppEmpty.protoTypes = {
children: PropTypes.element.isRequired,
};
function mapsStateToProps(state){
return {
menuRight: state.menuRight,
isActive: state.isActive,
};
}
export default connect (mapsStateToProps, {setReferences})(AppEmpty);
The documentation says:
getScrollParent Function Override method to return a different scroll listener if it's not the immediate parent of InfiniteScroll.
Can someone suggest a way to pass that reference in an easy way to the <InfiniteScroll/>?
After trying everything, I realized that I can pass as a refs using document.getElementById ('comp_id_a_referenciar') :(
Content-> compomente_base -> form_busqueda, Tables -> InfiniteScroll(Refs_Content).
<div>
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true || false}
loader={<div className="loader" key={0}>Loading ...</div>}
useWindow={false}
getScrollParent={ () => document.getElementById('content') }
>
{items}
</InfiniteScroll>
working! nice!

Resources