Hi, Shopify dev community -
In our checkout extension I’m working to change to using “s-text-field” instead of the old “TextInput” component. Our inputs are controlled.
I’ve noticed one thing that wasn’t happening before:
- We have a metafield that’s a JSON with things like “{month: 9, day: 10}”
- We have two independent text fields that update different keys within the same metafield (e.g. an input for “month”, and an input for “date”)
- When tabbing from the “month” field to the “day” field, sometimes the input for the day “snaps back” to an empty string (presumably because the update from the “month” field has now finished)
Here’s a gif of it in practice, notice the “bbb” in the second input clears while i keep typing:

A few questions:
- In the old checkout components, onChange was highly recommended over onInput. Is that guidance still the same? We could use onInput here to avoid the delay, but obviously that would add more re-renderes to the app.
- Any other work arounds? Ideally we only have one metafield JSON for our app data. I would hate to have different metafields for different sub-fields.
- Is this behavior expected? I only ask because prior to this upgrade everything “worked”
Thanks! And in cases it helps, here’s my minimal reproduction (very messy - sorry!) from the gif above:
const applyMetafieldsChange = useApplyMetafieldsChange();
const [value] = useAppMetafields({ namespace: "$app", key: "myMetafield" });
const parsedValue = value?.metafield.value
? JSON.parse(value?.metafield.value)
: {};
console.log("value", value?.metafield.value);
return (
<>
<s-text-field
label="Controlled component - metafield state"
name="name"
value={parsedValue?.a}
autocomplete="off"
onChange={(e) => {
console.log("Would update state:", {
...parsedValue,
a: e.currentTarget.value,
});
applyMetafieldsChange({
type: "updateCartMetafield",
metafield: {
namespace: "$app",
key: "myMetafield",
value: JSON.stringify({
...parsedValue,
a: e.currentTarget.value,
}),
type: "json",
},
});
}}
/>
<s-text-field
label="Second controlled component - metafield state"
name="name"
value={parsedValue?.b}
autoComplete="off"
onChange={(e) => {
console.log("Would update state 2:", {
...parsedValue,
b: e.currentTarget.value,
});
applyMetafieldsChange({
type: "updateCartMetafield",
metafield: {
namespace: "$app",
key: "myMetafield",
value: JSON.stringify({
...parsedValue,
b: e.currentTarget.value,
}),
type: "json",
},
});
}}
/>
</>
);