Search code examples
reactjsreduxnext.jsswr

How to prevent component re-render on switching browser tabs?


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.

Default screen

When I filter the location with text then it does the filter. when I filter the location

But when I switch the tab it resets the result.

enter image description here

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

Solution

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