Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect if dynamically imported component has loaded in Nextjs

This is a simplification on my scenario

import React, { useState } from 'react';
import dynamic from 'next/dynamic';

const DynamicImportedComponent = dynamic(() => import('Foo/baz'), {
  ssr: false,
  loading: () => <p>loading..</p>
});


const MyComponent: React.FC = () => {
  const [show, setShow] = useState(false)

  const variableComponent = <SomeStyledComponent styled={show}>value of show is {show.toString()}</SomeStyledComponent>
  return (
    <>
    {variableComponent}
    { show && <DynamicImportedComponent />}
    <button onClick={() => setShow(!show)}>toggle</button>
    </>
  )
}

There is a toggle state that allows me to show or not a component that is dynamically loaded

I have a component that renders always, but based on the toggle might show different styles.
When the user clicks the button, the state sets to true and the component is dynamically loaded. In the meantime, nextjs render the loading message. So far, so good

However, I'd like the variableComponent to be invisible while the DynamicImportedComponent is being loaded. This is because it has some styles that don't quite look good with the loader. Is there a way to detect if the component has finished loading? Once the component is loaded, I would render the variableComponent as I would normally do

I was thinking of something along the lines of

{(DynamicImportedComponent.loaded || !DynamicImportedComponent.isLoading) && variableComponent}

but that does not seem to exist.

I'm using latest version of React and nextjs, and typescript

like image 545
Gonzalo.- Avatar asked Nov 06 '22 09:11

Gonzalo.-


1 Answers

I also ran into this issue. At the moment there isn't a standard way of doing this, but one of the Nextjs maintainers suggested the following in a discussion in the Nextjs repo:

We mirror React.lazy as much as possible (while also providing the missing loading state as suspense doesn't work server-side). Suspense might solve your case as it allows for a spinner to be shown for a part of the tree when one item suspends (eg the dynamic component). However as said Suspense is not available server-side currently.

One thing that you could potentially do is pass state in a context value and setState in the loading component

I found that the suggestion of using React Context works well.

Example code from my project

const ToolbarTray: FunctionComponent<{}> = () => {
  const { isDialogOpen, closeDialog, openDialog } = useDialogState();

  const [ isDialogLoading, setDialogLoadingStatus ] = useState(false);

  return (
    <DynamicLoadingContext.Provider value={setDialogLoadingStatus}>
      <Button onClick={openDialog}>
        {isDialogLoading ? <CircularProgress /> : <EditIcon />}
      </Fab>

      {isDialogOpen && <EditPersonDialog onClose={closeDialog} />}
    </DynamicLoadingContext.Provider>
  );
};

const EditPersonDialog = dynamic(() => import('~/dialogs/edit-person-dialog'), {
  ssr: false,
  loading: () => {
    const setLoading = useContext(DynamicLoadingContext);

    useEffect(() => {
      setLoading(true);
      return () => setLoading(false);
    }, [setLoading]);

    return null;
  },
});
like image 171
John Avatar answered Nov 15 '22 05:11

John