6

Fetching data using Axios and useEffect results in null before the actual object is loaded.

Unfortunately, I am not able to use object destructuring before the actual object is not empty.

I am forced to use a hook to check whether an object is empty or not.

For instance, I would like to create multiple functions and split my code up in separate functions for better readability.

This is my HTTP request Hook:

import { useState, useEffect } from 'react';

import axios from 'axios';

export const useHttp = (url, dependencies) => {
    const [isLoading, setIsLoading] = useState(false);
    const [fetchedData, setFetchedData] = useState(null);

    useEffect(() => {
        setIsLoading(true);

        axios
            .get(url)
            .then(response => {
                setIsLoading(false);
                setFetchedData(response.data);
            })
            .catch(error => {
                console.error('Oops!', error);
                setIsLoading(false);
            });
    }, dependencies);

    return [isLoading, fetchedData];
};

Followed by my page component:

import React from 'react';

import { PAGE_ABOUT, API_URL } from 'constants/import';

import Header from './sections/Header';
import Main from './sections/Main';
import Aside from './sections/Aside';

import { useHttp } from 'hooks/http';

const About = () => {
    const [isLoading, about] = useHttp(PAGE_ABOUT, []);

    if (!isLoading && about) {
        return (
            <section className="about">
                <div className="row">
                    <Header
                        featuredImage={API_URL + about.page_featured_image.path}
                        authorImage={API_URL + about.page_author_image.path}
                        authorImageMeta={about.page_author_image.meta.title}
                        title={about.about_title}
                        subtitle={about.about_subtitle}
                    />

                    <Main
                        title={about.page_title}
                        content={about.page_content}
                    />

                    <Aside
                        title={about.about_title}
                        content={about.about_content}
                    />
                </div>
            </section>
        );
    }
};

export default React.memo(About);

The actual problem I am not able to nest functions before the object is actually returned.

Is there a way to fetch data without a check by any chance? or a cleaner solution would help.

I would like to use multiple components to split up the code.

Any advice or suggestion would be highly appreciated.

4
  • I'm really not sure which part of the code is causing you problems? Can you explain a bit more? Commented Jul 16, 2019 at 11:43
  • I'm in the same boat as Zeljko, I'm not sure what the actual question/problem is. Commented Jul 16, 2019 at 13:42
  • I have this "if (!isLoading && about) {"I am not able to add an arrow function outside this IF statement because it first returns null causing my app to return undefined. I would like to use object destructuring instead of having about.title, about.section i just want to use "title" and "section". And I want to split op the complete code into multiple inline components but I can't do that outside the if statement. Is there a way I can only return the data if it's loaded without the check? Commented Jul 16, 2019 at 15:27
  • 2
    Can you create a stackblitz/codesandbox demo showing the issue? Commented Jul 16, 2019 at 15:57

3 Answers 3

7
+50

What you are asking would be bad for UIs. You don't want to block the UI rendering when you're fetching data. So the common practice is showing a Loading spinner or (if you're counting on the request being fast) just rendering nothing until it pops up.

So you would have something like:

const About = () => {
    const [isLoading, about] = useHttp(PAGE_ABOUT, []);

    if (isLoading) return null; // or <Loading />

    return (
       <section className="about">
            <div className="row">
                <Header
                    featuredImage={API_URL + about.page_featured_image.path}
                    authorImage={API_URL + about.page_author_image.path}
                    authorImageMeta={about.page_author_image.meta.title}
                    title={about.about_title}
                    subtitle={about.about_subtitle}
                />

                <Main
                  title={about.page_title}
                  content={about.page_content}
                />

                <Aside
                  title={about.about_title}
                  content={about.about_content}
                />
            </div>
        </section>
    );
};

If your api has no protection for errors and you're afraid of about being null or undefined you can wrap the component with an Error Boundary Component and show a default error. But that depends if you use those in your app.

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

Sign up to request clarification or add additional context in comments.

Comments

0

You are not returnig any if your condition in our About component is false so add a case if isLoading or !about you could add a loader maybe:

const About = () => {
    const [isLoading, about] = useHttp(PAGE_ABOUT, []);

    let conditionalComponent =  null;

    if (!isLoading && about) {
        conditionalComponent= (
            <section className="about">
                <div className="row">
                    <Header
                        featuredImage={API_URL + about.page_featured_image.path}
                        authorImage={API_URL + about.page_author_image.path}
                        authorImageMeta={about.page_author_image.meta.title}
                        title={about.about_title}
                        subtitle={about.about_subtitle}
                    />

                    <Main
                        title={about.page_title}
                        content={about.page_content}
                    />

                    <Aside
                        title={about.about_title}
                        content={about.about_content}
                    />
                </div>
            </section>
        );
    }
    return conditionalComponent

};

Comments

0

As I recognize you want to get rid of condition if (!isLoading && about) {

You can make it in several ways:

1) You can create Conditional component by your own. This component should receive children prop as a function and should apply other props to this function:

function Conditional({ children, ...other }) {
  if (Object.entries(other).some(([key, value]) => !value)) {
    return "empty"; // null or some `<Loading />` component
  }
  return children(other);
}

Also I made a small demo https://codesandbox.io/s/upbeat-cori-ngenk

It helps you to create only the one place for condition and reuse it in dozens of components :)

2) You can use some library, which is built on a top of React.lazy and React.Suspense (but it wouldn't work on a server-side). For example https://dev.to/charlesstover/react-suspense-with-the-fetch-api-374j and a library: https://github.com/CharlesStover/fetch-suspense

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.