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>
</>
)
}
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
})