thowitz
thowitz

Reputation: 48

React Next app not working as expected, most likely hook updating issue

I've been banging my head against my desk for the past hour trying to figure out why the following code only displays the loading spinner and never updates to show the actual data even when I can see the data logged in the console, so I know the data is actually being fetched.

What is supposed to happen is that the page will display a spinner loader the first time the data is retrieved and then after useSWR has finished getting the data, the page will re-render to show the data. The data will eventually be shown in like a globe thing, but just as a prototype, I'm rendering it just with .map. The page is also not supposed to show the spinner a second time.

I think my problem might have something to do with calling the function to update the hook at the very end causes the whole page to re-render, although I'm not sure and if that is the case, I couldn't figure out how I'd make that work considering how SWR spontaneously re-fetches the data.

Here's the primary file:

import { useEffect, useState } from "react";
import Navbar from "@components/navbar";
import Globe from "@/components/interest-globes/globe";

import usePercentages from "@/components/interest-globes/globe-percentages";
const globeName = "primary";

const App = () => {
  const [globePercentages, setGlobePercentages] = useState([]);
  const [isFirstLoad, setFirstLoad] = useState(false);
  const [isLoading, setLoading] = useState(false);
  const [isError, setError] = useState();

  useEffect(() => {
    setFirstLoad(true);
  }, []);

  let {
    globePercentages: newGlobePercentages,
    isLoading: newLoadingState,
    isError: newErrorState,
  } = usePercentages(globeName);

  console.log(newGlobePercentages, newLoadingState, newErrorState);

  useEffect(() => {
    updateGlobe();
  }, [newGlobePercentages, newLoadingState, newErrorState]);

  const updateGlobe = () => {
    if (
      isFirstLoad &&
      (newGlobePercentages !== null || newGlobePercentages !== "undefined")
    ) {
      setFirstLoad(false);
    } else if (newLoadingState || !newGlobePercentages) {
      setLoading(true);
      return;
    } else if (newErrorState) {
      setError(newErrorState);
      return;
    }

    setGlobePercentages(newGlobePercentages);
  };

  return (
    <div>
      <Navbar />
      <div className="container-md">
        <h1>The percentages are:</h1>
        <br />
        <Globe
          globePercentages={globePercentages}
          isLoading={isLoading}
          isError={isError}
        />
        {/* <Globe props={{ globePercentages, isLoading, isError }} /> */}
        <br />
      </div>
    </div>
  );
};

export default App;

Here is the globe component:

import Loading from "@components/loading";
import Error from "@components/error";

const Globe = ({ globePercentages, isLoading, isError }) => {
// const Globe = ({ props }) => {
//   let { globePercentages, isLoading, isError } = props;

  if (isLoading) {
    return <Loading/>
  }
  else if (isError) {
    return <Error/>
  }

  return (
    <div>
      {globePercentages.map((interest) => (
        <div key={interest.name}>
          <h2>Name: {interest.name}</h2>
          <h4>Globe Percentage: {interest.globePercentage}%</h4>
          <br />
        </div>
      ))}
    </div>
  );
};

export default Globe;

Here is use-percentages:

import { useEffect } from "react";
import useSWR from "swr";
import fetcher from "@components/fetcher";

const usePercentages = (globeName) => {
  const url = `/api/v1/interest-globes/${globeName}/get-globe-percentages`;
  let { data, error } = useSWR(url, fetcher, { refreshInterval: 1000 });

  return {
    globePercentages: data,
    isLoading: !error && !data,
    isError: error,
  };
};

export default usePercentages;

Upvotes: 0

Views: 290

Answers (1)

Adam Jenkins
Adam Jenkins

Reputation: 55772

If you move your hook into your Globe component, everything becomes so simple:

const App = () => {

  return (
    <div>
      <Navbar />
      <div className="container-md">
        <h1>The percentages are:</h1>
        <br />
        <Globe globeName={'primary'}/>
        <br />
      </div>
    </div>
  );
};

export default App;
const Globe = ({globeName}) => {

  const lastPercentages = useRef(null);

  const {
    globePercentages,
    isLoading,
    isError,
  } = usePercentages(globeName);

  // only show the loader if isLoading AND lastPercentages is false-y
  if (isLoading && !lastPercentages.current) {
    return <Loading/>
  }

  if (isError) {
    return <Error/>
  } 
  
  lastPercentages.current = globePercentages ?? lastPercentages.current;

  return (
    <div>
      {lastPercentages.current.map((interest) => (
        <div key={interest.name}>
          <h2>Name: {interest.name}</h2>
          <h4>Globe Percentage: {interest.globePercentage}%</h4>
          <br />
        </div>
      ))}
    </div>
  );  

}

The source of truth of globePercentages, isLoading, and isError come from the usePercentages hook. It feels so complicated because you're duplicating these pieces of data into new state variables, and then requiring that you keep them in sync. They are meant to be used as sources of truth, don't recreate them in your own state variables.

Upvotes: 1

Related Questions