우리는 이전 Deep Dive에서 Dynamic Import에 대해서 살펴 보았습니다. 그런데, 만약에 다운되는데 많은 시간이 필요한 패키지라면 문제가 있을 수 있습니다. 따라서 우리는 2가지 방법으로 이 문제를 해결해 보도록 하겠습니다.
/pages/enter.tsx
import { Suspense, useEffect, useState } from "react";
import type { NextPage } from "next";
import useMutation from "@libs/client/useMutation";
import { cls } from "@libs/client/utils";
import Button from "@components/button";
import Input from "@components/Input";
import { useForm } from "react-hook-form";
import type { SubmitHandler } from "react-hook-form";
import { ConversationList } from "twilio/lib/rest/conversations/v1/service/conversation";
import { useRouter } from "next/router";
import dynamic from "next/dynamic";
// import Bs from "@components/bs";
const Bs = dynamic(
() =>
new Promise((resolve) =>
setTimeout(() => resolve(import("@components/bs")), 10000)
),
{
ssr: false,
// loading: () => <span>Loading a big component 4 u bby</span>,
suspense: true,
}
);
type MethodType = "email" | "phone";
interface EnterForm {
email?: string;
phone?: string;
}
interface TokenForm {
token: string;
}
interface EnterMutationResult {
ok?: boolean;
error?: string;
}
interface MutationResult {
ok?: boolean;
error?: string;
}
const Enter: NextPage = () => {
// 사용자가 로그인하기 위해서 email or phone을 쳐서 token을 발급받는 과정
const [enter, { loading, data, error }] =
useMutation<EnterMutationResult>("/api/users/enter");
// token을 검증하는 과정
const [
confirmToken,
{
loading: tokenLoading,
data: tokenData,
error: tokenError,
},
] = useMutation<MutationResult>("/api/users/confirm");
const [submitting, setSubmitting] = useState(false);
const { register, handleSubmit, reset } = useForm<EnterForm>();
const {
register: tokenRegister,
handleSubmit: tokenHandleSubmit,
} = useForm<TokenForm>();
const [method, setMethod] = useState<MethodType>("email");
const onEmailClick = () => {
reset();
setMethod("email");
};
const onPhoneClick = () => {
reset();
setMethod("phone");
};
const onValid: SubmitHandler<EnterForm> = (validForm) => {
enter(validForm);
};
const onTokenValid = (validForm: TokenForm) => {
if (tokenLoading) return;
confirmToken(validForm);
};
const router = useRouter();
// 만약 tokenData를 검증하는 API로부터 검증하는 요청이 들어왔고
// ok가 true라면 기본페이지로 리다이렉팅 시킨다.
// 반대로 로그인하지 않은 사용자가 / 홈페이지로 들어올려교 하면 /enter로 보내는 작업이 수반되어야 할 것이다.
useEffect(() => {
if (tokenData?.ok) {
router.push("/");
}
}, [tokenData, router]);
return (
<div className="mt-16 px-4">
<h3 className="text-center text-3xl font-bold">
Enter to Carrot
</h3>
<div className="mt-8">
{data?.ok ? (
<form
onSubmit={tokenHandleSubmit(onTokenValid)}
className="mt-8 flex flex-col"
>
<Input
register={tokenRegister("token", {
required: true,
})}
name="token"
label="Confirmation Token"
type="number"
required
/>
<Button
text={tokenLoading ? "Loading" : "Confirm Token"}
/>
</form>
) : (
<>
<div className="flex flex-col items-center">
<h5 className="text-sm font-medium text-gray-500">
Enter using:
</h5>
<div className="mt-8 grid w-full grid-cols-2 gap-16 border-b">
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "email"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onEmailClick}
>
Email
</button>
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "phone"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onPhoneClick}
>
Phone
</button>
</div>
</div>
<form
onSubmit={handleSubmit(onValid)}
className="mt-8 flex flex-col"
>
{method === "email" ? (
<Input
register={register("email", {
required: true,
})}
name="email"
label="Email address"
type="email"
/>
) : null}
{method === "phone" ? (
<>
{/* <Bs /> */}
<Suspense fallback="Loading something big">
<Bs />
</Suspense>
<Input
register={register("phone", {
required: true,
})}
name="phone"
label="Phone number"
type="number"
kind="phone"
/>
</>
) : null}
{method === "email" ? (
<Button
text={loading ? "Loading" : "Get login link"}
/>
) : null}
{method === "phone" ? (
<Button
text={
loading ? "Loading" : "Get one-time password"
}
/>
) : null}
</form>
</>
)}
<div className="mt-8">
<div className="relative">
<div className="absolute w-full border-t border-gray-300" />
<div className="relative -top-3 text-center ">
<span className="bg-white px-2 text-sm text-gray-500">
Or enter with
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
</svg>
</button>
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
);
};
export default Enter;
다음과 같이 suspense옵션을 true로 주는 것입니다. 우선 저는 Bs컴포넌트를 불러오는데, 10초를 걸리게 해놓았습니다. 그리고 'next/dynamic'은 React의 Lazy loading패키지를 상속받은 것이므로 Suspense컴포넌트를 활용할 수 있게 됩니다. 그래서
{/* <Bs /> */}
<Suspense fallback="Loading something big">
<Bs />
</Suspense>
다음과 같이 falolback으로 로딩중에 표시해주고 싶은 htm코드를 적어주시면 됩니다. 당연히 jsx문법 다 가능합니다.
정확히 잘 동작하는 것을 확인할 수 있습니다.
이 외에도 dynamic의 옵션에 loading옵션으로 () => <span>Loading...</span> 이렇게 주어도 상관없습니다. 다만 코드의 깔끔함을 원하신다면 전자를 활용하시는걸 추첝드립니다.
'Web > NextJs' 카테고리의 다른 글
[ Next.js - DeepDive ] - Script Component (0) | 2022.08.06 |
---|---|
[ Next.js - DeepDive ] - _document and Fonts (0) | 2022.08.04 |
[ Next.js - DeepDive ] - Dynamic Import (0) | 2022.08.04 |
[ Next.js - DeepDive ] - Responses and Redirections (0) | 2022.08.04 |
[ Next.js - DeepDive ] -미들웨어 ( Middlewares ) (0) | 2022.08.04 |