reactjsrerender

Update the DOM without refreshing the page in React.js after fetching more data


I want to implement dynamic pagination with intersection observer API and it works as intended: scrolling down the page, new data is fetched but after that the page is scrolled to its beginning. I know that it happens because react re-renders the page after loading new info. But can I somehow prevent this?

Here is my code:


function AllUniversititesList() {
  const [loading, setLoading] = useState(false);
  const [listOfUnis, setListOfUnis] = useState([]);
  const [isSearched, setSearched] = useState(false);
  const lastElement = useRef();
  const observer = useRef();

  async function fetchAllUnis() {
    setLoading(true);
    const result = await SearchService.getAllUniversities(); //return of data from API
    setListOfUnis([...listOfUnis, ...result]);
    setLoading(false);
  }

  useEffect(() => {
    if(!listOfUnis.length) {
          fetchAllUnis();
    }
  }, [])

  useEffect(() => {
    if(loading) return;
    if(observer.current) observer.current.disconnect();
     const callback = (entries, observer) => {
      if(entries[0].isIntersecting) {
        fetchAllUnis()
      }
     }
     observer.current = new IntersectionObserver(callback);
     observer.current.observe(lastElement.current);
  }, [loading])

return (
    <section className='min-h-screen bg-slate-200 pt-[7%] '>
         {!loading && listOfUnis && listOfUnis.length > 0 &&  <UniversitiesList info = {listOfUnis}/>}
     <div className='w-full h-4' ref={lastElement}></div>
    </section>
  )
}


Solution

  • The main reason why your page is always scrolling at the top, is because you are conditionally rendering the entire UniversitiesList component based whether some data have been loaded or not.

    In order to avoid this I would suggest a structural change on your code. Place all the observer logic and state in the UniversitiesList component. This will allow you to render new items without causing the list to scroll at the top every time.

    An example implementation of what I have just mentioned would look like:

    function UniversititesList() {
      const [loading, setLoading] = useState(false);
      const [listOfUnis, setListOfUnis] = useState([]);
      const [isSearched, setSearched] = useState(false);
      const lastElement = useRef();
      const observer = useRef();
    
      async function fetchAllUnis() {
        setLoading(true);
        const result = await SearchService.getAllUniversities(); //return of data from API
        setListOfUnis([...listOfUnis, ...result]);
        setLoading(false);
      }
    
      useEffect(() => {
        if(!listOfUnis.length) {
              fetchAllUnis();
        }
      }, [])
    
      useEffect(() => {
        if(loading) return;
        if(observer.current) observer.current.disconnect();
         const callback = (entries, observer) => {
          if(entries[0].isIntersecting) {
            fetchAllUnis()
          }
         }
         observer.current = new IntersectionObserver(callback);
         observer.current.observe(lastElement.current);
      }, [loading])
    
      const renderUniList() {
    
       if(loading) {
        return <div>Is loading...</div>
       }
    
       if(!loading && listOfUnis.length === 0) {
        return <div>No data available...</div>
       }
    
       return listOfUnis.map((uni) => 
       // return your prefered jsx element here
       )
      }
    
    return (
        <section className='min-h-screen bg-slate-200 pt-[7%] '>
         <div className="uni-list">
         {renderUniList()}
         </div>
         <div className='w-full h-4' ref={lastElement}></div>
        </section>
      )
    }