Colin Ricardo
Colin Ricardo

Reputation: 17239

Why does conditionally imported component not render?

I'm trying to make a simple blog.

What I want to do is conditionally import a specific component based on the url params (id below).

However this code only renders Loading, it never changes. Why is this?

import Layout from "../../components/Layout";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

const LoadingPost = () => {
  return <h1>Loading</h1>;
};

const Post = () => {
  const router = useRouter();
  const { id } = router.query;
  const [loaded, setLoaded] = useState(false);
  let PostToShow = LoadingPost;

  useEffect(() => {
    if (id) {
      import(`../../posts/${id}.tsx`).then(_ => {
        PostToShow = () => _;
        setLoaded(true);
      });
    }
  }, [id]);

  const renderPost = () => {
    if (loaded) {
      return <PostToShow />;
    }
  };

  return (
    <Layout>
      <h1>This would be a post</h1>
      <h2>The id of this post would be: {id}</h2>
      {renderPost()}
    </Layout>
  );
};

export default Post;

enter image description here

Upvotes: 0

Views: 38

Answers (3)

Colin Ricardo
Colin Ricardo

Reputation: 17239

The issue here is that we're importing a module, not just a component.

import Layout from "../../components/Layout";
import { useEffect, useState } from "react";

const LoadingPost = () => {
  return <h1>Loading</h1>;
};

const Post = ({ pageProps }) => {
  const { id } = pageProps;
  const [loaded, setLoaded] = useState(false);
  const [Component, setComponent] = useState(LoadingPost);

  useEffect(() => {
    if (id) {
      import(`../../posts/${id}.tsx`).then(_ => {
        setComponent(_);
        setLoaded(true);
      });
    }
  }, [id]);

  const renderPost = () => {
    if (loaded) {
      // @ts-ignore
      const Comp = Component.default;
      // @ts-ignore
      return <Comp />;
    }
  };

  return (
    <Layout>
      <h1>This would be a post</h1>
      <h2>The id of this post would be: {id}</h2>
      {renderPost()}
    </Layout>
  );
};

Post.getInitialProps = ctx => {
  const { id } = ctx.query;

  return {
    pageProps: {
      id,
    },
  };
};

export default Post;

As other people mentioned, we can do setComponent(_.default) either, but I couldn't get that working as it gives: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object..

The above works as intended:

enter image description here

Upvotes: 0

ant_1_b
ant_1_b

Reputation: 61

You're doing the following:

    import(`../../posts/${id}.tsx`).then(_ => {
        PostToShow = () => _;
        setLoaded(true);
    });

So you're changing the value for the PostToShow variable, then setting the state - which triggers a new render. However on ever render you do:

let PostToShow = LoadingPost;

Hence you always render the LoadingPost component.

Upvotes: 0

Zohaib Ijaz
Zohaib Ijaz

Reputation: 22875

Here is how you can do this

import(`../../posts/${id}.tsx`).then(module=> {
  PostToShow = module.default;
  setLoaded(true);
});

Upvotes: 1

Related Questions