Hello everyone,
I’m working on a custom discount app but faced a wierd issue. The Save Bar isn’t hiding after saving changes, changes are saved but seems like it still thinks the form is ‘dirty’. Can anyone help e figure what the issues is. Here is the code:
import { reactExtension,useApi, BlockStack,FunctionSettings,Section,Text,Form,NumberField,Heading} from "@shopify/ui-extensions-react/admin";
import { useState, useEffect } from "react";
const TARGET = "admin.discount-details.function-settings.render";
const METAFIELD_NAMESPACE = "$app:discounts--ui-extension";
const METAFIELD_KEY = "function-configuration";
const DEFAULT_FORM_STATE = {
product: 0
}
export default reactExtension(TARGET, async (api) =>
{
const existingDefinition = await getMetafieldDefinition(api.query);
if (!existingDefinition)
{
const metafieldDefinition = await createMetafieldDefinition(api.query);
}
return <App />;
});
function App() {
const {
applyExtensionMetafieldChange,
i18n,
formState,
updateFormField,
resetForm,
initialFormState,
loading
} = useExtensionData();
if (loading) {
return <Text>{i18n.translate("loading")}</Text>;
}
return (
<FunctionSettings onSave={applyExtensionMetafieldChange} onError={(errors) => { setError(errors[0]?.message) }}>
<Heading size={6}>{i18n.translate("title")}</Heading>
<Form onReset={resetForm}>
<Section>
<BlockStack gap="base">
<BlockStack gap="base">
<NumberField
label="Percentages"
name="product"
value={formState.product}
defaultValue={initialFormState.product}
onChange={(value) =>
{
updateFormField('product', Number(value))
}}
>
</NumberField>
</BlockStack>
</BlockStack>
</Section>
</Form>
</FunctionSettings>
);
}
function useExtensionData()
{
const { applyMetafieldChange, i18n, data } = useApi(TARGET);
const initialMetafields = data?.metafields || [];
const [savedMetafields] = useState(initialMetafields);
const [formState, setFormState] = useState({...DEFAULT_FORM_STATE});
const [initialFormState, setInitialFormState] = useState({...DEFAULT_FORM_STATE});
const [loading, setLoading] = useState(false);
const updateFormField = (field, value) =>
{
setFormState((prevState) =>
{
return {
...prevState, [field]:value
}
});
};
useEffect(() =>
{
async function fetchInitialData()
{
setLoading(true);
const metafieldValue = savedMetafields.find((metafield) => metafield.key === METAFIELD_KEY)?.value;
if (metafieldValue)
{
try
{
const parsedValue = JSON.parse(metafieldValue);
const initialState =
{
...DEFAULT_FORM_STATE, product: Number(parsedValue.product) ?? 0
}
setFormState(initialState);
setInitialFormState(initialState);
}
catch (error)
{
console.log(error);
}
}
setLoading(false);
}
fetchInitialData();
},
[initialMetafields]);
async function applyExtensionMetafieldChange()
{
try
{
await applyMetafieldChange(
{
type: "updateMetafield",
namespace: METAFIELD_NAMESPACE,
key: METAFIELD_KEY,
value: JSON.stringify(formState),
valueType: "json",
});
setInitialFormState({...formState});
return true;
}
catch (error)
{
return false;
}
}
function resetForm()
{
setFormState(initialFormState);
return true;
}
return {
applyExtensionMetafieldChange,
i18n,
formState,
updateFormField,
resetForm,
initialFormState,
loading
};
}
async function getMetafieldDefinition(adminApiQuery) {
const query = `#graphql
query GetMetafieldDefinition {
metafieldDefinitions(first: 1, ownerType: DISCOUNT, namespace: "${METAFIELD_NAMESPACE}", key: "${METAFIELD_KEY}") {
nodes {
id
}
}
}
`;
const result = await adminApiQuery(query);
return result?.data?.metafieldDefinitions?.nodes[0];
}
async function createMetafieldDefinition(adminApiQuery) {
const definition = {
access: {
admin: "MERCHANT_READ_WRITE",
},
key: METAFIELD_KEY,
name: "Discount Configuration",
namespace: METAFIELD_NAMESPACE,
ownerType: "DISCOUNT",
type: "json",
};
const query = `#graphql
mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
metafieldDefinitionCreate(definition: $definition) {
createdDefinition {
id
}
}
}
`;
const variables = { definition };
const result = await adminApiQuery(query, { variables });
return result?.data?.metafieldDefinitionCreate?.createdDefinition;
}
Tried almost everything but either I’m missing something obvious or it’s a bug in the react component.
Kind regards.