Rendering of Macro Body in Forge Confluence Macro

Hi there,

I am new to Forge and I am writing my first macro that allows me to hide or show the contents in its body dependent on the value of a hide parameter.

It basically works and there is no problem when the page is first rendered after its update. But on subsequent reloads of the page, the macro body’s content is shown shortly on the page before the code of the macro is called to hide it.

I am using UI Kit (render: native and layout: bodied) and I do not see how I can prevent the body to be shown first.

What am I missing?

Cheers,
Robert

Hey @rr1 without knowing how you’re implementing this, it’s difficult to say what the cause could be. Can you elaborate a little more on how you’re tracking whether the content is hidden or not, and how you’re rendering it?

Hi Melissa,

Thank you for your reply!

I am running the basic examples (from what is generated via the Forge CLI) and come up with this manifest:

modules:
  macro:
    - key: section-macro
      resource: r-section
      render: native
      resolver:
        function: resolver
      title:
        i18n: macro.section.name
      layout: bodied
      config: true
  function:
    - key: resolver
      handler: index.handler
resources:
  - key: r-section
    path: src/frontend/section-macro.jsx
translations:
  resources:
    - key: en-US
      path: src/locales/en-US.json
    - key: de-DE
      path: src/locales/de-DE.json
  fallback:
    default: en-US
app:
  runtime:
    name: nodejs22.x
    memoryMB: 256
    architecture: arm64
  id: ari:cloud:ecosystem::app/<SNIP>

Here is the JSX file:

import React, {useEffect, useState} from 'react';
import ForgeReconciler, {
  I18nProvider,
  Text,
  useConfig,
  useProductContext,
  useTranslation
} from '@forge/react';
import {invoke} from '@forge/bridge';
import {createConfig} from "./section-macro-config";


const defaultConfig = {"hide": false};
// https://developer.atlassian.com/platform/forge/add-configuration-to-a-macro-with-ui-kit/
const Config = () => {
  const {ready, t} = useTranslation();
  if (!ready) {
    return null;
  }
  return createConfig(t);
};

const App = () => {
  const context = useProductContext();
  const macroBody = context?.extension?.macro?.body;

  const actualConfig = useConfig();
  const config = actualConfig || defaultConfig;


  console.log("...Config: " + JSON.stringify(config, null, 2));
  console.log("...Context: " + JSON.stringify(context, null, 2));
  console.log("...Macro Body: " + JSON.stringify(macroBody, null, 2));

  const [data, setData] = useState();
  useEffect(() => {
    invoke('getText', {example: 'my-invoke-variable'}).then(setData);
  }, [context]);

  //  !config.options || !config.options.includes("hide")
  if (!config.hide && (config.options && config.options !== "hide")) {
    return data ?
      <>
        <Text>ok {data}!</Text>
      </>
      :
      <>
        <Text>Pending ...</Text>
      </>;
  }
  return (<>
  </>);
};

ForgeReconciler.render(
  <React.StrictMode>
    <App/>
  </React.StrictMode>
);
ForgeReconciler.addConfig(
  <React.StrictMode>
    <I18nProvider>
      <Config/>
    </I18nProvider>
  </React.StrictMode>);

Please ignore the use of the checkbox. The code worked better with the Select, but I am still investigating using that widget …

The config code from the referenced ./section-macro-config is this:

import React from 'react';
import {CheckboxGroup, Label} from '@forge/react';

export function createConfig(t) {
// https://developer.atlassian.com/platform/forge/ui-kit/components/select/#props
  const options = [
    {value: "hide", label: t("macro.section.params.hide.label")}
  ];
  return (
    <>
      <Label labelFor="hide">{t("macro.section.params.hide.description")}</Label>
      <CheckboxGroup name="options" options={options}/>
    </>
  );
}

The resolver is not used since I have no backend code. I did not manage to remove it. So this code just sends back some text I display instead of the macro body:

import Resolver from ‘@forge/resolver’;

const resolver = new Resolver();

resolver.define(‘getText’, (request) => {
return ‘… world!’;
});

export const handler = resolver.getDefinitions();

I also do not add the locale files for brevity since I do not expect the problem there.

I removed the code to other macros that I am testing other “features” with. I do not assume that those macros have an influence as they do not interact with each other.

On the page I use the macro as follows:

So the final result is correct:

But before this result is rendered, I get this (when viewing the page on a reload):

With the reload the tables are shown quite a long time. When switching pages it is sometimes not seen and sometimes only pops up for a moment.

I assume I do not yet understand a fundamental concept?

Cheers,
Robert

I think that Confluence just caches the previous macro render, and shows the old macro version briefly. Somehow the cache eviction seems broken.

Hi @marc,

Thank you for your reply!

I can see that this may be the case. But should my code not prevent (or be able to prevent) the rendering of macro body completely? Preventing the caching of the fragment in the first place?

I am coming from Confluence DC where you have full control over what the macro renders. So I assume I am missing a basic Forge concept here …?

I don’t think you are missing anything. I think Forge is broken in that respect.