简体   繁体   中英

REST API Response Flickering on Page Load

I've got component that displays contact information from a dealer as chosen by the user. To be more specific, a user selects their location, setting a cookie which then is used to define the API call. I pull in the contact information of the dealer in that location using Axios, store it in a context, and then display the information as necessary through several components: the header, a "current location" component etc. However, I'm having an issue with the content flickering each time the page is refreshed.

I've tried storing the JSON response in local storage, but, for a brief moment on page load, it shows as undefined, making the flicker continue. Obviously, I'm needing to eliminate that so that the data persists.

I've got it working via ApiContext , and I'm displaying the data in my Header component. Below is the code for both:

ApiContext.tsx

import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';

const contextObject = {} as any;

export const context = createContext(contextObject);

export const ApiContext = ({ children }: any) => {
  const [selectedDealer, setselectedDealer] = useState(`1`);

  useEffect(() => {
    axios
      .get(`${process.env.GATSBY_API_ENDPOINT}/${selectedDealer}`)
      .then((response) => setselectedDealer(response.data));
  }, [selectedDealer]);

  const changeDealer = (id: any) => {
    setselectedDealer(id);
  };

  const { Provider } = context;
  return (
    <Provider value={{ data: selectedDealer, changeDealer: changeDealer }}>
      {children}
    </Provider>
  );
};

Header.tsx

import React, { ReactNode, useContext, useEffect, useState } from 'react';

import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';

import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';

interface HeaderProps {
  navigationContent: ReactNode;
}

export const Header = ({ navigationContent }: HeaderProps) => {
  const [scrolled, setScrolled] = useState(false);
  const [open, setOpen] = useState(false);

  const data = useContext(context);
  
  const buttonLabel = data ? data.name : 'Find a Dealer';
  const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';

  useEffect(() => {
    const handleScroll = () => {
      const isScrolled = window.scrollY > 10;
      if (isScrolled !== scrolled) {
        setScrolled(!scrolled);
      }
    };

    document.addEventListener('scroll', handleScroll, { passive: true });

    return () => {
      document.removeEventListener('scroll', handleScroll);
    };
  }, [scrolled]);

  return (
    <>
      <NotificationBar notificationContent={navigationContent} />
      <header className={scrolled ? css(s.header, s.header__scrolled) : s.header}>
        <nav className={s.header__navigation}>
          <ul className={s.header__container}>
            <li className={s.header__logo}>
              <Link to="/" className={s.header__link}>
                <Logo />
              </Link>
            </li>

            <li className={s.header__primary}>
              <MainNav navigationItems={navigationContent} />
            </li>

            <li className={s.header__utility}>
              <Button href={buttonLink}>{buttonLabel}</Button>
            </li>

            <li className={s.header__icon}>
              <MenuIcon onClick={() => setOpen(!open)} />
            </li>
          </ul>
        </nav>
      </header>
    </>
  );
};

I would assume that this is because the API call is being triggered each time the page is refreshed, so I wonder if there's any way to persist the data in a more efficient way?

Thanks in advance!

Your ApiContext.tsx could persist the data in localStorage is such a way:

import React, { createContext } from 'react';

import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';

const contextObject = {} as any;

export const context = createContext(contextObject);

const useAxios = makeUseAxios({
  axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});

const loadData = (cookie) => {
  const stored = localStorage.getItem("data");
  const parsed = JSON.parse(stored);
  // You can also store a lastSync timestamp along with the data, so that you can refresh them if necessary
  if (parsed) return parsed;
  const [{data}] = useAxios(`${cookie}`);
  if (!isEqual(parsed, data)) {
    localStorage.setItem('data', JSON.stringify(data));
  }
  return data
}

export const ApiContext = ({ children }: any) => {
  const [cookie] = useCookie('one-day-location', '1');
  const [{ data }] = loadData(cookie);

  const { Provider } = context;
  return <Provider value={data}>{children}</Provider>;
};

The above implementation will only fetch the data once, so remember to refresh them at some point inside your code and update the localStorage item, or use a timestamp to compare and force the api call as commented in my code.

Keep in mind that even this implementation may take a fraction of a second to be completed, so I would suggest to always use loaders/spinners/skeletons while your application is fetching the required data.

I got this worked out, using a hook that persists my state, storing it in a localStorage item.

usePersistState.ts

import { useEffect, useState } from 'react';

export const usePersistState = (key: string, defaultValue: string) => {
  const [value, setValue] = useState(() => {
    if (typeof window !== 'undefined') {
      const stickyValue = window.localStorage.getItem(key);
      return stickyValue !== null ? JSON.parse(stickyValue) : defaultValue;
    }
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue];
};

Then, in ApiContext , I set my default state, but when that state changes, it updates and persists the state. Here's my context component now:

ApiContext.tsx

import React, { createContext, useEffect } from 'react';

import { usePersistState } from 'hooks/use-persist-state';

import axios from 'axios';

const contextObject = {} as any;

export const context = createContext(contextObject);

const LOCAL_STORAGE_KEY_SELECTED_DEALER = 'selectedDealerInformation';

export const ApiContext = ({ children }: any) => {
  const [selectedDealer, setselectedDealer] = usePersistState(LOCAL_STORAGE_KEY_SELECTED_DEALER, '1');

  useEffect(() => {
    axios
      .get(`${process.env.GATSBY_API_ENDPOINT}/${selectedDealer}`)
      .then((response) => setselectedDealer(response.data));
  }, [selectedDealer]);

  const changeDealer = (id: any) => {
    setselectedDealer(id);
  };

  localStorage.setItem(LOCAL_STORAGE_KEY_SELECTED_DEALER, JSON.stringify(selectedDealer));

  const { Provider } = context;
  return (
    <Provider value={{ data: selectedDealer, changeDealer: changeDealer }}>{children}</Provider>
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM