简体   繁体   中英

Applying State to Nested Object in React. How do I call API and display with Use Effect

So I have been re-iterating my code and working around with it for a few hours and I need help. My goal was to use useEffect to load an API call from a horoscope API. I ran into this error where I was only able to see the data AFTER I loaded another page from the website and re-rendered the homepage. My goal was to see the data displayed initially on useEffect() from when I called it. However, it seems to not work. Not sure if I need to use setInitialState, or manipulate the way I am exporting the values through context.

On further speculation, I somewhat see what the problem is in my code, but I have no idea HOW to use setInitialState to load values in from the api.

          initialState.data.yesterday = data;

Do I save this to the a variable then call set data on that variable?

1.) I tried manipulating state best I could could.

2.) Page never loads data from API on first run, only shows data after I click a button

3.) Code seems to need setInitialDate but can't find reasonable solution mixed with useEffect to get the data right.

Thank you!!

I am willing to hear all optimal solutions for my problem. I am not even sure if I am going in the right direction. Thanks.


const [initialState, setInitialState] = useState({
    data: {
        yesterday: {},
        today: {},
        tomorrow: {},
    }
});

useEffect(() => {
    console.log("useEffect called");
    // eslint-disable-next-line react-hooks/exhaustive-deps
    async function getDayInfos() {
        try {
            const response = await axios.post('https://aztro.sameerkumar.website/?sign=aries&day=yesterday');
            const data = response.data
            initialState.data.yesterday = data;
        } catch (error) {
            console.error(error);
        }


        try {
            const response = await axios.post('https://aztro.sameerkumar.website/?sign=aries&day=today');
            initialState.data.today = response.data
        } catch (error) {
            console.error(error);
        }

        try {
            const response = await axios.post('https://aztro.sameerkumar.website/?sign=aries&day=tomorrow');
            initialState.data.tomorrow = response.data
        } catch (error) {
            console.error(error);
        }
    }

    getDayInfos()
    // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return <AstroContext.Provider value={{initialState}}>
    {children}
</AstroContext.Provider>

}

React needs you to call the setter (eg setInitialState ) in order to know that you've changed something that might require a re-render.

I understand it's tempting to try and do object manipulations when you have nested fields in your state, but given that your fields are changing basically all-at-once, we can do it atomically and improve your time-to-first-meaningful-data.

As an aside, I think initialState might not be a good idea for the name of your variable. Let's call it dayInfo because that's what it is. We also need to represent the state of the app while the data is being loaded - you'll be able to show a loading UI, you can get rid of that empty useEffect() dependency-array (and the ESLint disable comment) and it also really helps debugging.

Without any further ado, here's a working version:

// Document the shape of our state:
const initialState = {
  yesterday: {},
  today: {},
  tomorrow: {}
};

// Helper function that just gets a single day (to avoid repetition)
const fetchDay = async (day) => {
  try {
    const response = await axios.post(
      `https://aztro.sameerkumar.website/?sign=aries&day=${day}`
    );
    return response.data;
  } catch (error) {
    console.error(error);
  }
};

export default function App() {
  const [loadingState, setLoadingState] = useState("NOT_LOADED");
  const [dayInfo, setDayInfo] = useState(initialState);

  useEffect(() => {
    // Only go fetch the data if we haven't tried to yet
    if (loadingState === "NOT_LOADED") {
      setLoadingState("IN_PROGRESS");
      getDayInfos();
    }
  }, [loadingState]);

  async function getDayInfos() {
    // Fire off the three requests concurrently ...
    const yesterdayPromise = fetchDay("yesterday");
    const todayPromise = fetchDay("today");
    const tomorrowPromise = fetchDay("tomorrow");

    // ... but wait for them all to resolve here
    const [yesterday, today, tomorrow] = await Promise.all([
      yesterdayPromise,
      todayPromise,
      tomorrowPromise
    ]);

    setDayInfo({ yesterday, today, tomorrow });
    setLoadingState("LOADED");
  }

  ...
}

We've now clarified exactly when we're going to go and fetch the data.

We are also using Promises more effectively, firing off the three Axios fetches concurrently, rather than one-by-one. Finally, we atomically set the state of dayInfo and also indicate that we've got the data, to avoid fetching it ever again - much safer than an empty dependency array.

To @millhouse answer, I just add that it's always a good idea in React, or Redux, to keep your data as flatter as possible, since you need to make all immutable operations on your data to trigger the re-renders, and if you are not careful enough and have deep nested objects, you might find to have new objects with old referenced nested objects that cause all sort of subtle behaviours. If you are forced to use deep nested data structures, try Immer which is a great library that lets you to mutate an object deep nested property directly with the same result of a fully immutable operation.

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