使用 prisma 操作 db 會像這樣
export async function action({ request }) {
const formData = await request.formData();
const title = formData.get("title");
const badge_text = formData.get("badge_text");
const errors = {};
if (!title) {
errors.title = "Title is required.";
}
if (errors && Object.keys(errors).length > 0) {
return json(errors, { status: 400 });
}
const { session } = await authenticate.admin(request);
const shop = session.shop;
const randomId = [...crypto.getRandomValues(new Uint8Array(10))]
.map((x) => (x % 36).toString(36))
.join("");
await prisma.recommendationSet.create({
data: {
id: randomId,
shop,
title,
createdAt: new Date(),
updatedAt: new Date(),
},
});
return redirect("/app?success=1");
}
export async function loader({ request }) {
const { session } = await authenticate.admin(request);
const shop = session.shop;
const sets = await prisma.recommendationSet.findMany({
where: { shop },
orderBy: { createdAt: "desc" },
});
return {
plan: "free",
sets: sets.map((set) => ({
id: set.id,
title: set.title,
badge_text: "tbd",
count: 0,
createdAt: set.createdAt,
updatedAt: set.updatedAt,
})),
};
}
export async function loader({ params, request }) {
const { id } = params;
await authenticate.admin(request);
const { session } = await authenticate.admin(request);
const shop = session.shop;
const set = await prisma.recommendationSet.findUnique({
where: { shop, id },
});
return set;
}
export async function action({ request, params }) {
const formData = await request.formData();
const title = formData.get("title");
const badge_text = formData.get("badge_text");
const id = params.id;
const errors = {};
if (!title) {
errors.title = "Title is required.";
}
if (errors && Object.keys(errors).length > 0) {
return json(errors, { status: 400 });
}
const { session } = await authenticate.admin(request);
const shop = session.shop;
await prisma.recommendationSet.update({
data: {
title,
},
where: {
id,
shop,
},
});
return redirect("/app?success=1");
}
export async function action({ request }) {
const formData = await request.formData();
const id = formData.get("id");
await authenticate.admin(request);
const { session } = await authenticate.admin(request);
await prisma.recommendationSet.delete({
where: {
id,
shop: session.shop,
},
});
return redirect("/app?success=1");
}
操作 prisma 是還好 跟常見的 ORM 差不多就是那樣
比較需要思考的是 remix 是用 loader + action 來整合 ssr + csr 流程的開發體驗
是還不錯 但就是多一層抽象化
因此 雖然 action 是接收表單送出 但不是原生 html form 也不是 polaris form
而是有額外做好一些參數與設定的 remix react form
import { Form } from "@remix-run/react";
再來 如果不是全頁送出、會讓網址改變的 http post form submit
而是部份送出 類似 ajax 又想觸發 action 的話 要用 fetcher
const fetcher = useFetcher();
<RecommendationList
sets={sets}
onEdit={(id) => navigate(`/app/sets/${id}`)}
onDelete={(id) => {
fetcher.submit({ id }, { method: "post" });
}}
/>
因為是 remix 獨創的 ssr + csr 解決方案 所以傳統的 http get / http post / form submit / ajax 觀念不夠用
需要多學 remix 的 action / loader / fetcher 觀念
屬於必要之惡吧!用多一層抽象化來解決
還算優雅 但就是要學習成本 多一點認知負擔
除此之外 在做連結的時候 還需要小心別搞錯這兩個
import { Link } from "@shopify/polaris";
import { Link } from "@remix-run/react";
因為 admin panel 的 app 是包在 iframe 裡面
會有 hmac
host
id_token
locale
session
shop
timestamp
等等參數
基本上想在 iframe 裡面點連結 去影響整個 admin panel 這很複雜
經典意義上的 a 元素不可能自己做 要支援「右鍵開新分頁」更困難
我決定都先用 navigate()
來做導航就好了 先求簡單、先求穩
import { useNavigate } from "@remix-run/react";
const navigate = useNavigate();
最後就是 我做了簡單的 loading spinner 或稱 pending state 或稱 busy state
因為我對這部分的 ux 很在意
要多學 remix 的 navigation 與 fetcher 觀念
再次強調
總之就是 remix 獨創的 ssr + csr 解決方案
所以傳統的 http get / http post / form submit / ajax 觀念不夠用
export default function App() {
const { apiKey } = useLoaderData();
const navigation = useNavigation();
const fetchers = useFetchers();
const isNavigationLoading = navigation.state !== "idle";
const isFetcherLoading = fetchers.some(
(f) => f.state !== "idle" && f.state !== "uninitialized",
);
const isLoading = isNavigationLoading || isFetcherLoading;
return (
<AppProvider isEmbeddedApp apiKey={apiKey}>
{isLoading && (
<>
<Backdrop />
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 9999,
backgroundColor: "rgba(255,255,255,0.3)", // 淡淡遮罩
}}
>
<Spinner size="large" />
</div>
</>
)}
<NavMenu>
<Link to="/app" rel="home">
Home
</Link>
<Link to="/app/additional">Additional page</Link>
<Link to="/app/contact">Contact Me</Link>
</NavMenu>
<Outlet />
</AppProvider>
);
}
簡單做一下,效果非常好!
老話一句
shopify app 開發
我覺得門檻真的很高~