I have 3000+ tools data stored in my API, and I have used react-inifinite-scroll-component in order to show paginated view to the user, so that once they scroll so then only, they must see new data. I have a common useState called data1, where I concat the new data and I have used same state in order to render the cards. But what I can feel that once I render lot of cards and then again, I scroll back so I can see blank screen and after some time I see my cards back, ideally I don't want this behavior and need to provide smooth scrolling to the user, how can I do this. I have used React.memo() also in order to wrap my component. This is how I have implemented it.
<InfiniteScroll
dataLength={data1.length}
next={handleInfiniteScroll}
hasMore={hasMore}
loader={<div style={{
display: 'flex',
justifyContent: 'center',
textAlign: 'center',
marginBottom: '2rem',
alignItems: 'center'
}}><CircularProgress color="success" /></div>}
endMessage={
!loading && (
(toolsCount === 0 && value) ?
<NoResult subCategory={subCategory} />
:
(exclude) ? <div
style={{
display: 'flex',
justifyContent: 'center',
textAlign: 'center',
marginBottom: '2rem',
alignItems: 'center'
}}
>
<Link href={`../category/${subCategory}`}>
<a style={{
fontSize: '1.1rem',
color: '#8EA7E9',
fontWeight: 'bold',
textDecoration: 'underline'
}}>
See All Alternatives
</a>
</Link>
</div>
:
<AllSeen />
)
||
loading && (
<div style={{
display: 'flex',
justifyContent: 'center',
textAlign: 'center',
marginBottom: '2rem',
alignItems: 'center'
}}><CircularProgress color="success" /></div>
)
}
scrollThreshold={0.9}
>
<Grid container spacing={2} className={styles.productRoot}>
{data1.slice(0, 3).map((item: any, index: number) => (
<Grid key={item.slug} item xs={12} sm={6} md={4}>
<div>
<ProductCard
page={page}
item={item}
sort={sort}
favoriteCount={favoriteCount}
favorites={favorites}
setFavorites={setFavorites}
setFavoriteCount={setFavoriteCount}
data1={data1}
setData1={setData1}
/>
</div>
</Grid>
))}
{
(!loading && (page >= 1 && data1.length > 0)) && <div className={styles.newsletterCard}>
<div className={styles.titleNewsText}>
<Typography variant='h6' component="h6">Explore valuable AI tools that offer significant benefits.</Typography>
<Typography variant='body1' component="h6">Get weekly updates on new AI tools by subscribing to our newsletter and joining our community. <a href="/newsletter">(Read Past Issues)</a></Typography>
</div>
<div className={styles.textFieldNews}>
<TextField
id="outlined-error-helper-text"
placeholder="Email"
type="email"
value={email}
size="medium"
onChange={handleEmailChange}
required
error={!validEmail && email !== ""}
helperText={(validEmail || email === "") ? '' : ''}
style={{
background: '#142335',
borderRadius: '12px'
}}
/>
<Button
endIcon={loading1 ? <CircularProgress size={20} /> : (
<ArrowForwardIcon style={{ fontSize: '1.2em' }} />
)}
onClick={() => {
(validEmail && email !== "") ? (loading1 ? enqueueSnackbar('Please Wait!', { variant: 'warning' }) : postEmail(email)) : enqueueSnackbar('Please Enter Correct Email ID!', { variant: 'error' })
}}
>Subscribe</Button>
</div>
</div>
}
{/* Render the remaining cards */}
{data1.slice(3).map((item: any, index: number) => (
<Grid key={item.slug} item xs={12} sm={6} md={4}>
<div>
<ProductCard
page={page}
item={item}
sort={sort}
favoriteCount={favoriteCount}
favorites={favorites}
setFavorites={setFavorites}
setFavoriteCount={setFavoriteCount}
data1={data1}
setData1={setData1}
/>
</div>
</Grid>
))}
</Grid>
</InfiniteScroll>
I am stacking up the new data like this.
setData1((prevData: any) => prevData.concat(newData));
This is the code of my ProductCard component
import React, { useState } from 'react'
import styles from "../Cards.module.scss"
import Image from 'next/image'
import Link from 'next/link'
import { Card, CardActions, CardContent, CardMedia, Typography, Button } from '@mui/material'
import FavoriteBorderOutlinedIcon from '@mui/icons-material/FavoriteBorderOutlined';
import FavoriteOutlinedIcon from '@mui/icons-material/FavoriteOutlined';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import DoneIcon from '@mui/icons-material/Done';
import LockOpenIcon from '@mui/icons-material/LockOpen';
import LockIcon from '@mui/icons-material/Lock';
import SellIcon from '@mui/icons-material/Sell';
import PaidIcon from '@mui/icons-material/Paid';
import { makeFavourite, removeFavourite } from 'components/api/tool'
import { enqueueSnackbar } from 'notistack';
import { useSession, signIn, signOut } from "next-auth/react"
interface Props {
page: any;
item: any,
sort: string | undefined,
favoriteCount: any;
favorites: any;
setFavorites: any;
setFavoriteCount: any;
data1: any;
setData1: any;
}
const ProductCard = ({ page, item, sort, favoriteCount, favorites, setFavorites, setFavoriteCount, data1, setData1 }: Props) => {
const { data: session } = useSession<any>();
// Compute the values for dynamic rendering.
const title = item.verified_by_admin ? 'Featured' : item.name;
const showBorder = item.verified_by_admin && sort === 'verified';
const showPriceDiv = item.price_text && item.price_text !== '[]';
const showFeatureDiv = item.verified_by_admin && sort === 'verified';
const name = item.name;
const slug = item.slug;
const image_url = item.new_image_url;
const price_text = item.price_text;
const favorite_count = item.favorite_count;
const short_description = item.short_description;
const pricing = item.pricing;
const subcategories = item.subcategories;
const homepage_url = item.homepage_url;
const id = item.id;
const cacheKey = `${page}_response_${sort}`;
let icon = null;
switch (pricing[0]) {
case 'Free':
icon = <DoneIcon fontSize='small' />;
break;
case 'Freemium':
icon = <LockOpenIcon fontSize='small' />;
break;
case 'Paid':
icon = <PaidIcon fontSize='small' />;
break;
case 'Contact for Pricing':
icon = <LockIcon fontSize='small' />;
break;
case 'Free Trial':
icon = <LockOpenIcon fontSize='small' />;
break;
case 'Deals':
icon = <SellIcon fontSize='small' />;
break;
default:
icon = null;
}
const handleButtonClick = (item: any) => {
if (!session) {
enqueueSnackbar('Please login!', { variant: 'error' });
} else {
if (favorites[item.id]) {
// Item is already favorited, so remove it from favorites
// Update the API or perform necessary actions accordingly
setFavorites((prevFavorites: any) => ({ ...prevFavorites, [item.id]: false }));
setFavoriteCount((prevCount: any) => ({ ...prevCount, [item.id]: prevCount[item.id] - 1 }))
removeFavourite([item.id])
enqueueSnackbar('Tool unfavorited!', { variant: 'info' })
// Update the session storage data for the item
const newData1 = data1.map((dataItem: any) => {
if (dataItem.id === item.id) {
return { ...dataItem, is_favorite: false, favorite_count: dataItem[item.id] - 1 };
}
return dataItem;
});
setData1(newData1);
sessionStorage.setItem(cacheKey, JSON.stringify(newData1));
} else {
// Item is not favorited, so add it to favorites
// Update the API or perform necessary actions accordingly
setFavorites((prevFavorites: any) => ({ ...prevFavorites, [item.id]: true }));
setFavoriteCount((prevCount: any) => ({ ...prevCount, [item.id]: prevCount[item.id] + 1 }))
makeFavourite([item.id])
enqueueSnackbar('Tool favorited!', { variant: 'success' })
// Update the session storage data for the item
const newData1 = data1.map((dataItem: any) => {
if (dataItem.id === item.id) {
return { ...dataItem, is_favorite: true, favorite_count: dataItem[item.id] + 1 };
}
return dataItem;
});
setData1(newData1);
sessionStorage.setItem(cacheKey, JSON.stringify(newData1));
}
}
}
return (
<Card
key={slug}
title={title}
sx={{ maxWidth: 310, margin: 'auto' }}
style={showBorder ? { border: '2px solid #29b6f6' } : {}}
className={styles.cardDivRoot}
elevation={0}
>
{showPriceDiv && (
<div className={styles.priceUpperDiv}>
$ {price_text}
</div>
)}
{showFeatureDiv && (
<div className={styles.featureUpperDiv}>
Featured
</div>
)}
<Link href="/tools/[main]"
as={`/tools/${slug}`}
>
{/* For the time being we have removed target blank for now */}
<a target="_blank">
{/* <CardMedia
>
<div style={{ position: 'relative', width: '100%', height: '180px' }} >
<Image src={item.image_url} layout='fill' objectFit='cover' alt={item.name} placeholder="blur" blurDataURL='/blurImg.jpeg'/>
</div>
</CardMedia> */}
<CardMedia
>
<div style={{ position: 'relative', width: '100%', height: '180px' }} >
<Image src={image_url} layout='fill' objectFit='cover' alt={name} />
</div>
</CardMedia>
<CardContent className={styles.cardContent}>
<div className={styles.cardTitle}>
<Typography gutterBottom variant="h4" component="div">
{name}
</Typography>
<div className={styles.titleSaveDiv} title={`${item.favorite_count} favourited this tool`}>
<FavoriteBorderOutlinedIcon fontSize='small' />
{/* {item.favorite_count} */}
{favoriteCount[item.id]}
</div>
</div>
<Typography variant="body2" component="div" className={styles.cardShortDescription}>
{short_description}
</Typography>
<div className={styles.cardType}>
{icon}
{pricing[0]}
</div>
<div className={styles.cardHastags}>
{item.subcategories &&
item.subcategories.map((subCat: any) => (
<Typography key={subCat} variant='body2' component="div">
<div className={styles.circle}></div>
{subCat}
</Typography>
))}
</div>
</CardContent>
</a>
</Link>
<CardActions className={styles.cardBtnMain} >
<Button size="small" title='Visit the website' onClick={() => {
window.open(item.homepage_url)
}}><OpenInNewIcon /></Button>
<Button size='small'
onClick={() => handleButtonClick(item)}
variant='outlined' title='Add to favourite'>
{favorites[item.id] ? (
<FavoriteOutlinedIcon className={styles.buttonIcon} />
) : (
<FavoriteBorderOutlinedIcon className={styles.buttonIcon} />
)}
</Button>
</CardActions>
</Card>
)
}
export default React.memo(ProductCard)
Is there any way to optimize the performance. Here is the website: AI Toolhouse
Please help me out folks!
Need to optimize the performance on scrolling