Search code examples
reactjsasynchronousstatepreloader

Do I need an async function to properly include a working preloader component?


I'm working with an Electron React app, and I'm curious if I need an asynchronous function to properly display a preloader svg. The svg is stored in state, and when I test it out by showing it and not removing it, it works perfectly fine. However, when I actually try to use it in the process of my app, it never seems to show up.

Here's what I'd like it to look like (I achieve this by commenting out where I change the state back to null after processing files)

Here's what I get instead (What happens when I change the state from the loader back to null... there's no loader in sight!)

My question is, is this something I need an async function to achieve? I'm not extremely familiar with properly using async await, and this isn't the first time I've run into this issue in a react app. I'm just generally trying to figure out why the loader never seems to appear when I implement it as intended. I honestly may just be making a newbie error here, so I'd like a second opinion!

in App.js:

const [loading, setLoading] = useState(null);

const handleLoading = (value) => {
    setLoading(value);
};

...

<Route exact path="/list">
    <List loading={loading} />
</Route>
<Route exact path="/">
    <Start loading={handleLoading} />
</Route>

in Start.js

const handleFiles = (files) => {
            // *set the loading state to the component
            props.loading(
                <div className="base-loader">
                    <div className="preloader-wrapper big active">
                        <div className="spinner-layer spinner-blue-only">
                            <div className="circle-clipper left">
                                <div className="circle"></div>
                            </div>
                            <div className="gap-patch">
                                <div className="circle"></div>
                            </div>
                            <div className="circle-clipper right">
                                <div className="circle"></div>
                            </div>
                        </div>
                    </div>
                </div>
            );
            // counter to list with a Materialize toast when finished if > 0
            sendFiles(files);
            if (incompatibleFiles > 0) {
                M.toast({
                    html: `${incompatibleFiles} file(s) could not be minified due to incompatible file type.`,
                });
            }
            // reset for next drag and drop
            incompatibleFiles = 0;

            // *THIS is the line I comment out for it to start working (although it just goes infinitely)
            props.loading(null);
        };

And lastly in List.js, I simply just include {props.loading} in the component return statement.


Solution

  • It is likely because the loading is instant and the loader doesn't appear long enough to notice. If you try setting a timeout before setting the loading state to false the loader should be noticeable:

    setTimeout(() => {
      setLoading(false);
    }, 500);
    

    However, I think it is not necessary to fake the loading behavior if it happens quick enough. The best practice is probably to start showing a loader after 500ms-1000ms or something when the average human user starts to feel that your application isn't responsive if there is no change.

    let timeout = setTimeout(() => {
      if (!dataHasLoaded) {
        setLoading(true);
      } else {
        clearTimeout(timeout);
      }
    }, 500);