Because of this behavior, we cannot rely on this line 7 in LOOPER v2.1 to have the “Iteration” initialized to 0.
We need make sure the prop “Iteration” is initialized to 0 in the app side before changing the “Begin Loop” prop to Yes.
The behavior you’re seeing is expected: getValue() is asynchronous when used with a callback, so calling it directly like getValue(“N”) inside your add_log() statements will likely return undefined, as the value hasn’t yet been set or retrieved at that point.
To better assist you, could you please share what you’re trying to achieve with this sequence of setting and logging values? Understanding the goal will help us suggest an appropriate solution or workaround.
Thank you for pointing this out—you’re absolutely right that in Looper v2.1 the app must set Iteration = 0 before toggling Begin Loop = Yes, otherwise the first loop may start with an undefined value.
The good news is that Looper v3 (rolling out tomorrow, 19 June 2025) takes care of this for you:
Automatic initialization – When Begin Loop transitions to Yes, Looper v3 now resets Iteration to 0 behind the scenes, so no extra step is required in your app.
Start from Index – If you need to begin the loop somewhere other than the first item, the new Start from Index input lets you specify the initial position explicitly. Setting this to, say, 5 will start the first pass with Iteration = 5.
Once the update is live, you can remove any manual initialization you were doing and, if needed, use Start from Index to “jump” into the list.
could you please share what you’re trying to achieve with this sequence of setting and logging values?
I am just trying to understand the true behavior of setValue() and getValue() so that I can use it correctly inside my widgets.
Please let me know if it is documented somewhere.
I am extremely interested in Looper v3. I will definitely check it out!
Happy to dig a little deeper into what’s happening under the hood.
The key point is that setValue() and getValue() are not local, in‑memory calls inside the same JavaScript context. Your custom widget is running in its own <iframe>, while the Tulip app lives in the parent page. The two exchange data through postMessage, so every read or write is an asynchronous round‑trip across that iframe boundary.
What actually happens
setValue("N", x)
The widget posts a message to the parent frame saying “please store N = x”.
The call returns immediately—no guarantee the parent has processed it yet.
Parent frame
Receives the message, updates its internal value store, and then sends a confirmation message back to the widget.
Widget
When the confirmation arrives, the widget’s local cache is updated.
Until this message arrives, a subsequent getValue("N") will still return the previous value (or undefined).
Because network‑style messaging is involved, even though it’s all happening on the same machine the whole trip still takes at least one event‑loop tick.
Minimal delay example
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function runProcesses() {
setValue("N", 5);
await delay(100); // waiting 0,1s
let pN = getValue("N");
console.log(pN);
}
runProcesses();
A setTimeout(..., 0) (or await Promise.resolve()) simply defers execution to the next event‑loop cycle, which is enough time for the parent frame to process the message and reply.
Tip: In most real‑world flows you don’t need to fetch the value right after you set it—the app already “knows” the value you just sent. If you’re only logging for debugging, the delay trick above is perfect; if you need to react to the change inside your widget, consider subscribing to the widget’s value‑change event instead.
Hope this clarifies why getValue() was showing undefined and how to work around it. Let me know if you’d like more detail or code samples!