Item state available for oh-repeater config only after additional refresh

For some reason oh-repeater does not calculate the rangeStop expression below which is based on the length of a json string coming from an item state. Only after a page refresh the expression is calculated.

I have seen a similiar behaviour with oh-context where item states are also only available after a page refresh.

So just asking if this behaviour is explainable or a bug.

The funny part is the expression is calculated correctly in a Label component before and after the repeater.

Test widget:

uid: test
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Jan 26, 2025, 12:28:06 PM
component: f7-page
config: {}
slots:
  default:
    - component: oh-context
      config:
        constants:
          linesPerPage: 30
        variables:
          navPageId: =(vars.navPageId || 1)
      slots:
        default:
          - component: f7-block
            config: {}
            slots:
              default:
                - component: Label
                  config:
                    text: ="Test (calc in a block before the repeater's block):" + Math.ceil(JSON.parse(@@'vItemListFull').length / const.linesPerPage)
          - component: f7-block
            slots:
              default:
                - component: Label
                  config:
                    text: ="Test (calc in the same block as the repeater):" + Math.ceil(JSON.parse(@@'vItemListFull').length / const.linesPerPage)
                - component: f7-segmented
                  config:
                    color: gray
                    style:
                      margin: 5px 0px 7px 0px
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          for: r_page
                          fragment: true
                          rangeStart: 1
                          rangeStep: 1
                          rangeStop: =Math.ceil(JSON.parse(@@'vItemListFull').length / const.linesPerPage)
                          sourceType: range
                        slots:
                          default:
                            - component: oh-button
                              config:
                                action: variable
                                actionVariable: navPageId
                                actionVariableValue: =loop.r_page
                                outline: true
                                raised: false
                                style:
                                  --f7-button-bg-color: =(loop.r_page == vars.navPageId) ? '#C8C8C8':''
                                  --f7-button-border-width: 1px
                                  width: 50px
                                text: =loop.r_page
                - component: Label
                  config:
                    text: ="Test (calc after the repeater):" + Math.ceil(JSON.parse(@@'vItemListFull').length / const.linesPerPage)

Result:
On page load:
image

After page refresh:
image

Browser developer console output:

app.4b7e0bc….js:2 RangeError: Invalid array length
    at i.source (app.4b7e0bc….js:2:1140651)
    at e.get (app.4b7e0bc….js:2:1440424)
    at e.evaluate (app.4b7e0bc….js:2:1441420)
    at i._async_computed$source (app.4b7e0bc….js:2:1443615)
    at i.<anonymous> (app.4b7e0bc….js:2:1440252)
    at e.get (app.4b7e0bc….js:2:1440424)
    at new e (app.4b7e0bc….js:2:1440337)
    at i.$watch (app.4b7e0bc….js:2:1455887)
    at ja (app.4b7e0bc….js:2:1970761)
    at i.created (app.4b7e0bc….js:2:1970107)

Well, definitely a bug, but possibly also explainable. I think you are correct this is the same as, or at least related to, the oh-context error. I created an issue for that bug to make sure it doesn’t get forgotten:

But as you can see from that brief exchange, there’s not yet a clear solution.

What’s strange is that you see this effect in the range properties but no one has ever reported observing a similar issue in any of the other repeater properties. I find it hard to believe that no one has every used an item state in a repeater property before this. Although, as I think about it, I’m not sure that I ever have. It might be worth further testing to see if this restricted to rangeStop or is more widely present in other repeater properties.

Mee, too. But this simple test widget proofes.

I did some more testing and found out the following:

  1. @@'vItemListFull' resolves to "-". That’s why JSON.parse probably terminates without further procesing.

  2. By capturing this error by

rangeStop: =(@@'vItemListFull' ==
  "-")?1:Math.ceil((JSON.parse(@@'vItemListFull').length)
  / const.linesPerPage)

one would expect that rangeStop = 1, but in fact the second part is then correctly calculated and by “magic hand” I get rangeStop = 2.

You probably have an easy explanation for this :slight_smile:

Only partially. The "-" result for the state is the error but it is the expected value given that there is an error: any time you try to get the state of an item in a expression and that item doesn’t exist, you’ll get "-" as the result instead of an undefined error. What was going on in your initial code is that the "-" result produced a value that could not be parsed as a valid array range and this threw a javascript error which halted all further repeater function so you saw the error in the first place.

With your new code, you are successfully preventing the "-" result from causing a javascript error, so repeater code continues to work and produces a first widget result with the default “1” rangeStop. The first thing that still isn’t fully explained is why that item state call should fail and return "-" in the first place. The second thing that isn’t fully explained is that because the repeater javascript hasn’t been halted by an error, after calculating the repeater with the fake value, it then goes on to recalculate everything a second time. But this time it can get access to the item state and so the calculation proceeds correctly and the widget is rendered as expected. It’s just that the first failure happens so quickly, it’s hard to see and it looks like the expression succeeds on the first try.

You can see it better with this test.

uid: repeat_bug_test
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Jan 26, 2025, 11:18:37 AM
component: f7-block
config: {}
slots:
  default:
    - component: oh-repeater
      config:
        for: test1
        sourceType: =(@@'testString' == 'apple')?('array'):('range')
        rangeStart: 2
        rangeStop: 17
        in:
          - apple
          - banana
          - cucumber
      slots:
        default:
          - component: oh-button
            config:
              text: =loop.test1

If you replace the testString with a string item on your system and the test value with it’s actual state then this widget should result in the three buttons with the string text.

If you then 1) save that widget, 2) return to the main page, 3) refresh the browser, 4) open up that widget again, you will see the long list of numbered buttons flash briefly before being replaced by the correct buttons.

In addition to demonstrating the situation I described above, this shows that the problem is not just with the range properties, because in this case the expression is in the source property.

1 Like