Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

wonder95's avatar

Loading content on reka-ui SplitterPanel manual opening and not page mount

In my Vue 3 app, I am using the Splitter component from the reka-ui library, and I have it implemented in my App.vue:

and here is the child LookingGlassWrapper.vue

<script setup>
import LookingGlassPane from "@looking-glass/drawer/LookingGlassPane.vue";
import { useLookingGlass } from "@composables/useLookingGlass.js";

const { splitViewEnabled, nodeList, updateLookingGlassNodes } =
  useLookingGlass();

const loadNodes = async () => {
  if (Object.entries(nodeList.value).length === 0) {
    await updateLookingGlassNodes();
  }
};

await loadNodes();
</script>

<template>
  <div class="flex h-full divide-x-1 divide-black-900">
    <LookingGlassPane />
    <LookingGlassPane v-if="splitViewEnabled"></LookingGlassPane>
  </div>
</template>

However, I have the default state forced to be collapsed, which I have to override in onMounted in App.vue, because it seems that the default state is set to expanded:

onMounted(() => {
  // Set Looking Glass panel as closed by default.
  nextTick(() => {
    lookingGlassPanelRef.value?.collapse();
  });
});

The problem is, I only want to run the updateLookingGlassNodes() function when the user first opens the panel, not when the component is mounted (since they might not ever open it at all) and I need to do it in LookingGlassWrapper, since the same output is used in multiple child components. However, in order to also use <Suspense> in App.vue, the node loading has to happen asynchronously in the child components.

What is happening now, is that in LookingGlassWrapper, it is loaded on page mount. My first thought was to the @expand event in <SplitterPanel>, but since the default isexpanded, that function then runs before I set it to collapsed in onMounted().

How can I 1) only run updateLookingGlassNodes() async when the user opens the panel, 2) while still using <Suspense>?

0 likes
1 reply
LaryAI's avatar
Level 58

You’re correct: the challenge is that <Suspense> will mount the child component (and thus trigger its async logic) as soon as the panel is rendered in the DOM, regardless of whether the panel is visible/collapsed. The <SplitterPanel>'s @expand event is also fired on initial mount if the panel is expanded by default, which complicates things.

Goal:

  • Only load (and render) <LookingGlassWrapper> (and thus call updateLookingGlassNodes()) when the user first expands the panel.
  • Still use <Suspense> for async loading fallback.

Solution

Key idea:
Don’t render <LookingGlassWrapper> (and thus <Suspense>) until the panel is actually expanded for the first time.
You can do this by using a flag that you set on the <SplitterPanel>'s @expand event, and then use v-if to conditionally render the <Suspense> block.

1. Track if the panel has ever been expanded

Add a lookingGlassPanelActive ref in your parent (App.vue):

const lookingGlassPanelActive = ref(false);

function onLookingGlassPanelExpand() {
  // Only set to true the first time
  if (!lookingGlassPanelActive.value) {
    lookingGlassPanelActive.value = true;
  }
}

2. Use @expand on <SplitterPanel>

Update your <SplitterPanel> for Looking Glass to:

<SplitterPanel
  ref="lookingGlassPanelRef"
  class="bg-white"
  collapsible
  :collapsed-size="0"
  :min-size="0"
  @expand="onLookingGlassPanelExpand"
>
  <template v-if="lookingGlassPanelActive">
    <Suspense>
      <template #fallback>
        <p class="bg-red-700">Loading nodes...</p>
      </template>
      <LookingGlassWrapper />
    </Suspense>
  </template>
</SplitterPanel>
  • The <template v-if="lookingGlassPanelActive"> ensures the wrapper (and thus its async logic) only mounts when the user expands the panel.
  • The @expand event is only fired when the panel is expanded (not collapsed), so this works as intended.

3. Remove the await loadNodes() from LookingGlassWrapper's top-level

Instead, in LookingGlassWrapper.vue, you can keep your async logic as-is, since it will only run when the component is mounted (which now only happens when the user expands the panel):

<script setup>
import LookingGlassPane from "@looking-glass/drawer/LookingGlassPane.vue";
import { useLookingGlass } from "@composables/useLookingGlass.js";

const { splitViewEnabled, nodeList, updateLookingGlassNodes } = useLookingGlass();

const loadNodes = async () => {
  if (Object.entries(nodeList.value).length === 0) {
    await updateLookingGlassNodes();
  }
};

await loadNodes();
</script>

4. Summary

  • The panel is collapsed by default.
  • <LookingGlassWrapper> (and its async loading) is only mounted/rendered when the user expands the panel for the first time.
  • <Suspense> still works as expected for async fallback.

Full Example (relevant part)

App.vue:


This approach ensures your async loading logic only runs when the user opens the panel, while still leveraging <Suspense> for loading feedback.

Please or to participate in this conversation.