Deep Kakkar
Deep Kakkar

Reputation: 6297

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

Upvotes: 9

Views: 8741

Answers (2)

juliomalves
juliomalves

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

windmaomao
windmaomao

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,

  • if the Locations component repaints
  • if the locationsFetcher has refired

The 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

Related Questions