0

I'm trying to suspend and lazy load multiple components which have cross-dependencies. I cannot figure out how to ensure one suspended component awaits the other before attempting to load.

My scenario is as follows, I have a component called "CountryLanguages" and within that component two child components, "LanguageList" & "LanguageTranslations". These function as part of a "Country" page component.

  • All of these are lazy loaded with Suspense and a fallback assigned.
  • The Languages component has a useFetch hook called to get a list of languages for a particular country.
  • The LanguageList displays them, and the LanguageTranslations makes another useFetch hook to get a list of translations for that particular language.
  • A prop is passed (languageIso) from Languages to LanguageTranslations which contains the language ISO to make the API call on.

The question: Is it possible to have a Suspended component wait for the parent components useFetch to be completed before it attempts calling the API?

What I'm currently noticing with the below is that due to the component being suspended, the LanguageTranslations component fires off with the languageIso being undefined, therefore not rendering on first load.

// Country.jsx
... removed imports for brevity
const CountryIntro = lazy(() => import('./countryintro/CountryIntro'));
const CountryDetails = lazy(() => import('./countrydetails/CountryDetails'));
const CountryLanguages = lazy(() =>
    import('./countrylanguages/CountryLanguages')
);

function Country() {
    return (
        <>
            <Suspense fallback={<CountryIntroSkeleton />}>
                <CountryIntro />
            </Suspense>
            <Suspense fallback={<CountryDetailsSkeleton />}>
                <CountryDetails />
            </Suspense>
            <Suspense fallback={<CountryLanguagesSkeleton />}>
                <CountryLanguages />
            </Suspense>
        </>
    );
}

export default Country;
// CountryLanguages.jsx
... removed imports for brevity
const LanguageList = lazy(() => import('./LanguageList'));
const LanguageTranslations = lazy(() => import('./LanguageTranslations'));

function CountryLanguages() {
    const countryIso = useCountryIso();
    const { data } = useFetch(`/language/country/all?iso3=${countryIso}`);
    const [languageIso, setLanguageIso] = useState(data?.[0].iso);

    return (
        <Section name="Languages">
            <SectionHeader name="Languages" />
            <Suspense fallback={<CountryLanguagesSkeleton />}>
                <LanguageList
                    data={data}
                    languageIso={languageIso}
                    setLanguageIso={setLanguageIso}
                />
            </Suspense>
            <Suspense fallback={<TranslationSkeleton />}>
                <LanguageTranslations languageIso={languageIso} />
            </Suspense>
        </Section>
    );
}

export default CountryLanguages;
// LanguageList.jsx
function LanguageList({ data, languageIso, setLanguageIso }) {
    const languageList = data?.map((item) => {
        return (
            <p
                key={item.id}
                onClick={() => setLanguageIso(item.iso)}
                className={item.iso === languageIso ? 'active' : ''}>
                {item.name}
            </p>
        );
    });

    return (
        <>
            <div className="item-list">
                <h3>Most Spoken</h3>
                <div>{languageList}</div>
            </div>
        </>
    );
}

export default LanguageList;
// LanguageTranslations.jsx
... removed  imports for brevity
function LanguageTranslations({ languageIso }) {
    const { data } = useFetch(
        `/language/translation/all?iso=${languageIso}`,
        {},
        [languageIso]
    );

    const phrases = data?.map((item) => {
        return (
            <div
                key={item.id}
                className="country-language-phrase">
                <p>{item.phrase}</p>
                <p className="red-text">{item.translation}</p>
                <p className="subtitle red-text">
                    {item.phonetic == null
                        ? 'No phonetic spelling available'
                        : item.phonetic}
                </p>
                <span
                    className={`material-symbols-sharp ${
                        item.link === null ? 'disabled' : ''
                    }`}
                    onClick={() => window.open(item.link, '_blank')}
                    id="phrase-exit">
                    exit_to_app
                </span>
            </div>
        );
    });

    return (
        <div className="country-language-translations">
            <h3>Common Phrases</h3>
            <div>{phrases}</div>
        </div>
    );
}

export default LanguageTranslations;

Edit: Attempting what Alex mentioned (wrapping both components which call an API in one suspense) produces the same effect, so I can't see how the order of child components can influence the order in which they await before making the necessary API call.

// CountryLanguages.jsx
const LanguageList = lazy(() => import('./LanguageList'));
const LanguageTranslations = lazy(() => import('./LanguageTranslations'));

function CountryLanguages() {
    const [languageIso, setLanguageIso] = useState(null);

    return (
        <Section name="Languages">
            <SectionHeader name="Languages" />
            <Suspense fallback={<CountryLanguagesSkeleton />}>
                <LanguageList
                    languageIso={languageIso}
                    setLanguageIso={setLanguageIso}
                />
                <LanguageTranslations languageIso={languageIso} />
            </Suspense>
        </Section>
    );
}

// LanguageList.jsx
... removed imports for brevity
function LanguageList({ languageIso, setLanguageIso }) {
    const countryIso = useCountryIso();
    const { data } = useFetch(`/language/country/all?iso3=${countryIso}`);

    useEffect(() => {
        setLanguageIso(data?.[0].iso);
    }, []);

    const languageList = data?.map((item) => {
        return (
            <p
                key={item.id}
                onClick={() => setLanguageIso(item.iso)}
                className={item.iso === languageIso ? 'active' : ''}>
                {item.name}
            </p>
        );
    });

    return (
        <>
            <div className="item-list">
                <h3>Most Spoken</h3>
                <div>{languageList}</div>
            </div>
        </>
    );
}

[value not being set after LanguageList useEffect on render][1] [1]: https://i.stack.imgur.com/JYM4P.png

isherwood
  • 58,414
  • 16
  • 114
  • 157
Tim
  • 3
  • 3

0 Answers0