Reputation: 6297
In my Next.js app, the component is getting re-rendered when I change the browser tab and then get back to the tab in which the app is already opened. e.g. app is open tab 1 and when I switch to tab 2 and then come back to tab 1.
Actually, I have a page on which listing of records appears, so when I do local filter using text match it is working fine. But when I change the tab and get back to the app tab, it resets the listing again.
When I filter the location with text then it does the filter.
But when I switch the tab it resets the result.
I am using useSwr
for data fetching and display listing. Here below is code of component:
import useSWR from 'swr'
import Link from 'next/link'
import Httpservice from '@/services/Httpservice'
import { useState, useEffect, useCallback } from 'react'
import NavBar from '@/components/NavBar'
import Alert from 'react-bootstrap/Alert'
import Router, { useRouter } from 'next/router'
import NoDataFound from '@/components/NoDataFound'
import nextConfig from 'next.config'
import { useTranslation, useLanguageQuery, LanguageSwitcher } from 'next-export-i18n'
export default function Locations({...props}) {
const router = useRouter()
const { t } = useTranslation()
const [queryLanguage] = useLanguageQuery()
const httpService = new Httpservice
const pageLimit = nextConfig.PAGE_LIMIT
const [loading,setLoading] = useState(true)
const [pageIndex, setPageIndex] = useState(1)
const [locations, setLocations] = useState([])
const [searchText, setSearchText] = useState('')
const [locationId, setLocationId] = useState(null)
const [isExpanding, setIsExpending] = useState(null)
const [loadMoreBtn, setLoadMoreBtn] = useState(true)
const [locationName, setLocationName] = useState(null)
const [errorMessage, setErrorMessage] = useState(null)
const [tempLocations, setTempLocations] = useState([])
const [deleteMessage, setDeleteMessage] = useState(null)
const [successMessage, setSuccessMessage] = useState(null)
const [displayConfirmationModal, setDisplayConfirmationModal] = useState(false)
const showDeleteModal = (locationName, locationId) => {
setLocationName(locationName)
setLocationId(locationId)
setSuccessMessage(null)
setErrorMessage(null)
setDeleteMessage(`Are you sure you want to delete the '${locationName}'?`)
setDisplayConfirmationModal(true)
}
const hideConfirmationModal = () => {
setDisplayConfirmationModal(false)
}
const locationsFetcher = async() => {
try{
await httpService.get(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`).then((response) => {
if(response.status == 200 && response.data) {
let data = response.data.results
setLocations([...new Set([...locations,...data])])
setTempLocations([...new Set([...locations,...data])])
if(response.data.next == undefined && response.data.results.length == 0) {
setLoadMoreBtn(false)
}
setLoading(false)
setIsExpending(null)
return data
} else {
setLoading(false)
setIsExpending(null)
const error = new Error('An error occurred while fetching the data.')
error.info = response.json()
error.status = response.status
throw error
}
}).catch((error) => {
setLoading(false)
setIsExpending(null)
})
} catch (error) {
setLoading(false)
setIsExpending(null)
}
}
const {data, error} = useSWR(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`, locationsFetcher,{
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return
if (retryCount >= 10) return
setTimeout(() => revalidate({ retryCount }), 5000)
}
})
const loadMore = () => {
setPageIndex(pageIndex + 1)
setIsExpending(true)
}
const handleSearch = (e) => {
let searchKey = e.target.value
setSearchText(e.target.value)
if(searchKey.length > 0) {
console.log(tempLocations)
let foundValue = tempLocations.filter(location => location.name.toLowerCase().includes(searchText.toLowerCase()))
if(foundValue) {
setLoadMoreBtn(false)
setLocations(foundValue)
} else {
setLoadMoreBtn(true)
setLocations(tempLocations)
}
} else {
setLoadMoreBtn(true)
setLocations(tempLocations)
}
}
return (
<>
<NavBar />
<div className="app-wrapper">
<div className="app-content pt-3 p-md-3 p-lg-4">
<div className="container-xl">
<div className="row gy-4 mb-2">
<div className="col-12 col-lg-8">
<h1 className="page-head-title"> {t('locations')} </h1>
</div>
</div>
<div className="summary_col">
<div className="row gy-4">
<div className="col-12 col-lg-12">
<div className="dotted float-end">
<a href="javascript:void(0)">
<img src="/images/icons/dotted.png" width="16" height="4" alt="" />
</a>
</div>
</div>
</div>
<div className="row gy-4 mt-2">
<div className="col-6 col-lg-3 col-md-4">
<div className="input-group search_col">
<div className="form-outline ">
<input type="search" className="form-control" placeholder={t('search')} value={searchText} onChange={handleSearch} />
</div>
<button type="button" className="btn">
<img src="/images/icons/search.png" width="19" height="19" alt="" />
</button>
</div>
</div>
<div className="col-6 col-lg-9 col-md-8 ">
<Link href={{ pathname: '/settings/locations/add', query: (router.query.lang) ? 'lang='+router.query.lang : null }}>
<a className="btn btn-primary float-end">{t('location_page.add_location')}</a>
</Link>
</div>
</div>
<div className="row gy-4 mt-2">
<div className="col-12 col-lg-12">
<div className="vehicles_col table-responsive">
<table className="table" width="100%" cellPadding="0" cellSpacing="0">
<thead>
<tr>
<th>{t('location_page.name')}</th>
<th>{t('location_page.company')}</th>
<th>{t('location_page.contact')}</th>
<th>{t('location_page.email')}</th>
<th>{t('location_page.phone')}</th>
<th>{t('location_page.address')}</th>
<th>{t('detail')}</th>
</tr>
</thead>
<tbody>
{error && <tr><td><p>{t('error_in_loading')}</p></td></tr>}
{(loading) ? <tr><td colSpan="6"><p>{t('loading')}</p></td></tr> :
(locations && locations.length > 0) ? (locations.map((location, index) => (
<tr index={index} key={index}>
<td>{location.name}</td>
<td>
<a href="javascript:void(0)">
{(location.links && location.links.Company) ? location.links.Company : '-'}
</a>
</td>
<td>{location.contact}</td>
<td>{location.email}</td>
<td>{location.phone}</td>
<td>
{(location.address1) ? location.address1 : ''}
{(location.address2) ? ','+location.address2 : ''}
{(location.address3) ? ','+location.address3 : ''}
<br />
{(location.city) ? location.city : ''}
{(location.state) ? ','+location.state : ''}
{(location.country) ? ','+location.country : ''}
{(location.zip) ? ','+location.zip : ''}
</td>
<td>
<Link href={{ pathname: '/settings/locations/edit/'+ location.UUID, query: (router.query.lang) ? 'lang='+router.query.lang : null }}>
{t('view')}
</Link>
</td>
</tr>
))) : (<tr><td><NoDataFound /></td></tr>)}
</tbody>
</table>
<div className="click_btn">
{(loadMoreBtn) ? (isExpanding) ? t('loading') : <a href="javascript:void(0)" onClick={() => loadMore()}>
<span>{t('expand_table')}</span>
</a> : t('no_more_data_avail')}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
Upvotes: 9
Views: 8741
Reputation: 50248
By default useSWR
will automatically revalidate data when you re-focus a page or switch between tabs. This is what's causing the re-renders.
You can disable this behaviour through the options
object in your useSWR
call, by setting the revalidateOnFocus
field to false
.
useSWR(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`, locationsFetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return
if (retryCount >= 10) return
setTimeout(() => revalidate({ retryCount }), 5000)
},
revalidateOnFocus: false
})
Alternatively, you can use useSWRImmutable
(rather than useSWR
) to disable all kinds of automatic revalidations done by SWR.
import useSWRImmutable from 'swr/immutable'
// ...
useSWRImmutable(key, fetcher, options)
Which is essentially the same as calling:
useSWR(key, fetcher, {
// other options here
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
})
Upvotes: 7
Reputation: 7661
There are two debug points that i suggest to try first, although I believe the problem isn't caused by this component.
export default function Locations({...props}) {
console.log('Render')
and
const locationsFetcher = async() => {
console.log('Fetch')
The above are to confirm when switching tabs,
Locations
component repaintslocationsFetcher
has refiredThe above questions will help you to dig further. My guts feeling is that you have another piece in your code that detects the tab switching, ex. listening to the page active or not. Because by default this Locations
component shouldn't repaint by itself.
Upvotes: 0