React, show loading component inside navbar until data is fetched

Viewed 95

I have a navbar component that I pass options to it. One of these options is the title for menus. It used to be string but now one of the titles is the username so I have to pass a dynamic value which is fetched later.

I wanted to show a loading animation until it is fetched. So I pass this Loading component as username until the userName is fully fetched. How can I achieve this the right way? Here is a simplified version of my code:

App:

const App = () => {
  const [userName, setUserName] = useState(Loading);

  // fetching userName
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      body: JSON.stringify({
        userName: "YSLdev"
      }),
      headers: {
        "Content-type": "application/json; charset=UTF-8"
      }
    })
      .then((response) => response.json())
      .then((json) => {
        setUserName(json.userName);
      });
  }, []);

  // setting options to be shown in navbar
  const navBarOptions = [
    {
      name: userName,
      icon: "fingerprint",
      menuItems: [
        {
          href: "/camera",
          icon: "videocam",
          description: "Camera"
        },
        {
          href: "/data-analytics",
          icon: "stacked_bar_chart",
          description: "Data Analysis"
        }
      ]
    },
    {
      name: "Tools",
      icon: "settings",
      menuItems: [
        {
          href: "#",
          icon: "navigation",
          description: "Top View"
        },
        {
          href: "#",
          icon: "filter_alt",
          description: "Display Human"
        }
      ]
    }
  ];

  return (
    <div>
      <NavBar options={navBarOptions} />
    </div>
  );
};

NavBar:

import React from "react";
import styles from "./navbar.module.scss";

export default function NavBar({ options }) {
  return (
    <div className={styles["navbar"]}>
      {options &&
        options.map((option, index) => (
          <div key={index} className={styles["option"]}>
            <div className={`${styles["icon"]} material-icons`}>
              {option.icon}
            </div>
            <div className={styles["title"]}>{option.name}</div>
            <div className={styles["menu"]}>
              {option.menuItems.map((item, i) => (
                <div key={i}>{item.description}</div>
                // ... other tags
              ))}
            </div>
          </div>
        ))}
    </div>
  );
}

Loading:

export default function Loading({ size = "small" }) {
  switch (size) {
    case "small":
      return (
        <div className={styles["loadingio-spinner-spinner-5dnhr28eat2"]}>
          <div className={styles["ldio-53fz0lrw47f"]}>
            <div></div>
            <div></div>
            <div></div>
          </div>
        </div>
      );
    case "medium":
      return (
        <div className={styles["loadingio-spinner-spinner-8vfig2z3vf"]}>
          <div className={styles["ldio-w7mnwgpb5e"]}>
            <div></div>
            <div></div>
            <div></div>
          </div>
        </div>
      );
    default:
      return (
        <div className={styles["loadingio-spinner-spinner-8vfig2z3vf"]}>
          <div className={styles["ldio-w7mnwgpb5e"]}>
            <div></div>
            <div></div>
            <div></div>
          </div>
        </div>
      );
  }
}
2 Answers

An easy way to achieve what you want is to change your useState part to below code, as it's not correct to pass a component as parameter to it:

const [userName, setUserName] = useState("");

And change the userName object in navBarOptions array to this:

{
   name: userName ? userName : <Loading size="small" />,
  icon: "fingerprint",
  menuItems: [
    {
      href: "/camera",
      icon: "videocam",
      description: "Camera"
    },
    {
      href: "/data-analytics",
      icon: "stacked_bar_chart",
      description: "Data Analysis"
    }
  ] 
},

Forked working code here on CodeSandbox.

You must define a boolean state. and set it to true before sending a request to the api. And after getting the response, change it to false:

// if initial value set to true, you don't need to set it to true before send request to api
const [loading, setLoading] = useState(false);

useEffect(() => {
    setLoading(true);
    fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        body: JSON.stringify({
            userName: "YSLdev"
        }),
        headers: {
          "Content-type": "application/json; charset=UTF-8"
        }
    })
      .then((response) => response.json())
      .then((json) => {
          setUserName(json.userName);
      })
      .finally(() => {
          setLoading(false);
      });
  }, []);

then you can use loading state for conditional rendering.

Related