Conversations about creating, managing, and using metafields to store and retrieve custom data for apps and themes.
Here is my app._index.jsx file. I am new to the react and the Shopify model. I am trying to get the key from the formsubmit and save that value to metafield definition so each store/shop can have their own key saved. I want to use this key in my app at later stage. Think of this as a GA config key etc. The path to shopifyserver is actual path because ../shopify.server.js path was not being found.
I can see that the admin exists on the load but not when the action is triggered and i get undefined for the admin and the graphql is never triggered . Hope you can help.
import { useEffect } from "react";
import { json } from "@remix-run/node";
import { authenticate } from "/Users/ja/Desktop/ShopifyApp/new-data-publisher/app/shopify.server.js";
import { useLoaderData,useActionData, useNavigation, useSubmit } from "@remix-run/react";
import {
Page,
Layout,
Text,
Card,
Button,
BlockStack,
Box,
List,
Link,
InlineStack,
Form
} from "@shopify/polaris";
//console.log( authenticate);
export const loader = async ({ request }) => {
const { admin } = await authenticate.admin(request);
//return { admin }
return null;
};
export async function action({ request, newappkeyValue }) {
try {
const { admin } = await authenticate?.admin(request);
// Ensure admin object is available before proceeding
if (!admin) {
console.error('Error: Admin object not available.');
return json({ error: 'Admin object not available' }, { status: 500 });
}
const newAppKey = newappkeyValue;
const response = await admin.graphql(`
mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
metafieldDefinitionCreate(definition: $definition) {
createdDefinition {
id
name
namespace
key
description
}
userErrors {
field
message
code
}
}
}
`, {
variables: {
definition: {
name: "newAppkey",
namespace: "$app:new-appkey",
key: `${newAppKey}`,
description: "stores customers new app key.",
type: "list.single_line_text_field"
}
},
});
const responseJson = await response.json();
return json({
createdDefinition: responseJson.data.metafieldDefinitionCreate.createdDefinition,
userErrors: responseJson.data.metafieldDefinitionCreate.userErrors,
});
} catch (error) {
console.error('Error in action:', error.message);
return json({ error: 'Internal Server Error' }, { status: 500 });
}
}
export default function Index() {
const nav = useNavigation();
const actionData = useActionData();
const submit = useSubmit();
const isLoading =
["loading", "submitting"].includes(nav.state) && nav.formMethod === "POST";
//const newAppKey = actionData?.product?.id.replace(
// "gid://shopify/Product/",
// ""
//);
//useEffect(() => {
// if (newAppKey) {
// shopify.toast.show("newAppKey Added");
//}
//}, [newAppKey]);
//const generateProduct = () => submit({}, { replace: true, method: "POST" });
return (
<Page>
<ui-title-bar title="Publisher">
</ui-title-bar>
<BlockStack gap="500">
<Layout>
<Layout.Section>
<Card>
<BlockStack gap="500">
<BlockStack gap="200">
<Text as="h2" variant="headingMd">
Publisher app 🎉
</Text>
<Text variant="bodyMd" as="p">
This embedded app template uses{" "}
<Link
url="https://shopify.dev/docs/apps/tools/app-bridge"
target="_blank"
removeUnderline
>
App Bridge
</Link>{" "}
interface examples like an{" "}
<Link url="/app/additional" removeUnderline>
additional page in the app nav
</Link>
, as well as an{" "}
<Link
url="https://shopify.dev/docs/api/admin-graphql"
target="_blank"
removeUnderline
>
Admin GraphQL
</Link>{" "}
mutation demo, to provide a starting point for app
development.
</Text>
</BlockStack>
<BlockStack gap="200">
<Text as="h3" variant="headingMd">
Get started with products
</Text>
<Text as="p" variant="bodyMd">
Generate a product with GraphQL and get the JSON output for
that product. Learn more about the{" "}
<Link
url="https://shopify.dev/docs/api/admin-graphql/latest/mutations/productCreate"
target="_blank"
removeUnderline
>
productCreate
</Link>{" "}
mutation in our API references.
</Text>
</BlockStack>
<div>
<Text as="h3" variant="headingMd">
Please Enter your new AppKey.
</Text>
</div>
<br></br>
<Form method="post" onSubmit={async (event) => {
console.log('Form submitted!');
event.preventDefault();
// Additional logic
const formElement = event.target;
const formData = new FormData(formElement);
const newappkeyValue = formData.get('newappkey');
console.log('newappkeyValue', newappkeyValue);
await action({ request: event.request, newappkeyValue });
}}>
<input type="text" name="newappkey" placeholder="new App Key" size={60}/>
<button type="submit">Submit</button>
</Form>
<InlineStack gap="300">
</InlineStack>
</BlockStack>
</Card>
</Layout.Section>
<Layout.Section variant="oneThird">
<BlockStack gap="500">
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">
App template specs
</Text>
<BlockStack gap="200">
<InlineStack align="space-between">
<Text as="span" variant="bodyMd">
Framework
</Text>
<Link
url="https://remix.run"
target="_blank"
removeUnderline
>
Remix
</Link>
</InlineStack>
<InlineStack align="space-between">
<Text as="span" variant="bodyMd">
Database
</Text>
<Link
url="https://www.prisma.io/"
target="_blank"
removeUnderline
>
Prisma
</Link>
</InlineStack>
<InlineStack align="space-between">
<Text as="span" variant="bodyMd">
Interface
</Text>
<span>
<Link
url="https://polaris.shopify.com"
target="_blank"
removeUnderline
>
Polaris
</Link>
{", "}
<Link
url="https://shopify.dev/docs/apps/tools/app-bridge"
target="_blank"
removeUnderline
>
App Bridge
</Link>
</span>
</InlineStack>
<InlineStack align="space-between">
<Text as="span" variant="bodyMd">
API
</Text>
<Link
url="https://shopify.dev/docs/api/admin-graphql"
target="_blank"
removeUnderline
>
GraphQL API
</Link>
</InlineStack>
</BlockStack>
</BlockStack>
</Card>
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">
Next steps
</Text>
<List>
<List.Item>
Build an{" "}
<Link
url="https://shopify.dev/docs/apps/getting-started/build-app-example"
target="_blank"
removeUnderline
>
{" "}
example app
</Link>{" "}
to get started
</List.Item>
<List.Item>
Explore Shopify’s API with{" "}
<Link
url="https://shopify.dev/docs/apps/tools/graphiql-admin-api"
target="_blank"
removeUnderline
>
GraphiQL
</Link>
</List.Item>
</List>
</BlockStack>
</Card>
</BlockStack>
</Layout.Section>
</Layout>
</BlockStack>
</Page>
);
}
Hi, i have the same problem.
Have you already solved it?
Hi, I finally did it.
I found these 3 documents:
I did something like this:
export const loader = async ({ request }) => {
const { admin, session } = await authenticate.admin(request);
const responseJson = await FuntionToReadMetafields();
return json({
data: responseJson.data
});
};
export async function action({ request, params }) {
// Use the admin client to create or update metafields
const { admin, session } = await authenticate.admin(request);
const { shop } = session;
// read data from MyForm
const formData = {
...Object.fromEntries(await request.formData()),
shop,
};
// read single value
const { v1 } = formData;
FuntionToCreateOrUpdateMetafieldsMetafields();
}
const MyForm = ({ onSubmit }) => {
const formData = useLoaderData(); // read the current metafields value
const [v1, setV1Value] = useState(myFunction(formData));
const handleV1Change = useCallback(
(value) => setV1Value(value),
[],
);
// callback onSubmit with form data
const buttonClickAction = (event) => {
event.preventDefault();
onSubmit({ v1 });
};
return (
<Form onSubmit={buttonClickAction}>
<FormLayout>
<TextField
value={v1}
onChange={handleV1Change}
label="Value 1"
type="text"
autoComplete="off"
helpText=""
/>
<Button submit>Save</Button>
</FormLayout>
</Form>
);
};
export default function Index() {
const nav = useNavigation();
const submit = useSubmit();
const actionData = useActionData();
// call submit with form data to save
const handleFormSubmit = async (formData) => {
submit(formData, { replace: true, method: "POST" } );
};
return (
<Page>
<MyForm onSubmit={handleFormSubmit} />
</Page>
);
};
How did it work, On form submit to save a metafield value you would need to run a graphql query and will need admin.graphql method but this does not exist on action. Can you please share how did you update the metafields?