Rendering macros with react4xp

Hi. I’m trying to get macros to work in a project using react4xp.
I’m testing with a simple macro called blockquote. When I get the HtmlArea content in the controller, I get this:

<p>rich text before macro</p>

[blockquote text="simple test"/]

<p>rich text after macro</p>

When I process this with portal.processHtml, I get this:

<p>rich text before macro</p>
<p><!--#MACRO _name="blockquote" text="simple test" _document="__macroDocument2" _body=""--></p>
<p>rich text after macro</p>

In the frontend, that is rendered with error, like this:

<div id="pages_default____error__" style="border:1px solid #8B0000; padding:15px; background-color:#FFB6C1">
    <div class="react4xp-error" style="1px solid #8B0000; padding:15px; background-color:#FFB6C1; margin-bottom:15px">
        <style>
            li,h2,p,a,strong,span { font-family:monospace; }
            h2 { font-size:17px }
            li,p,a,strong,span { font-size:12px }
            a,span.data { color:#8B0000; }
        </style>
        <h2 class="react4xp-error-heading">React4xp SSR error</h2>
        
        <p class="react4xp-error-entry"><span class="jsxpath">Entry jsxPath: <span class="data">site/pages/default/default</span></span><br><span class="id">ID: <span class="data">pages_default__</span></span></p>

    </div>
<div id="pages_default__">
    <div class="react4xp-error" style="1px solid #8B0000; padding:15px; background-color:#FFB6C1; margin-bottom:15px">
        <style>
            li,h2,p,a,strong,span { font-family:monospace; }
            h2 { font-size:17px }
            li,p,a,strong,span { font-size:12px }
            a,span.data { color:#8B0000; }
        </style>
        <h2 class="react4xp-error-heading">React4xp SSR error</h2>
        
        <p class="react4xp-error-entry"><span class="jsxpath">Entry jsxPath: <span class="data">site/pages/default/default</span></span><br><span class="id">ID: <span class="data">pages_default__</span></span></p>

    </div>
</div>)
			}</div>

Screenshot of what’s rendered:
image

It looks like react4xp is not rendering the macro properly. Is there a way to fix this? I’m already rendering with the “ssr: true” option.

Are you getting any error messages in the XP log (or browser console)?

Can you provide source code of your macro (descriptor and controller)?

I have this in my xp log:

Caused by: org.graalvm.polyglot.PolyglotException: Error: Can't render <Regions> without a 'regionsData' prop.
	at <js>.Regions(<eval>:78)
	at <js>.renderWithHooks(<eval>:5671)
	at <js>.renderIndeterminateComponent(<eval>:5744)
	at <js>.renderElement(<eval>:5959)
	at <js>.renderNodeDestructiveImpl(<eval>:6117)
	at <js>.renderNodeDestructive(<eval>:6089)
	at <js>.renderNode(<eval>:6272)
	at <js>.renderHostElement(<eval>:5655)
	at <js>.renderElement(<eval>:5965)
	at <js>.renderNodeDestructiveImpl(<eval>:6117)
	at org.graalvm.sdk/org.graalvm.polyglot.Value.invokeMember(Value.java:973)
	at org.graalvm.js.scriptengine/com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.invokeMethod(GraalJSScriptEngine.java:558)
	... 181 more


ScriptException: org.graalvm.polyglot.PolyglotException: Error: Can't render <Regions> without a 'regionsData' prop.
in com.enonic.lib.react4xp.ssr.ServerSideRenderer.runSSR
Entry: 'site/pages/default/default'
Assets involved:
	runtime.js
	vendors.js
	templates.js
	site/pages/default/default.js
Failing call: 'SeedsReactReact4xp['site/pages/default/default'].default({"text":"text example"})'

SOLUTION TIPS: The previous error message may refer to lines in compiled/mangled code. To increase readability, you can try react4xp clientside-rendering or building react4xp with buildEnv = development or gradle CLI argument -Pdev. Remember to clear all cached behavior first (stop continuous builds, clear/rebuild your project, restart the XP server, clear browser cache).

Trying to make it work, I have made the macro as simple as possible:
blockquote.ts

import type { Enonic } from '@enonic/js-utils/types/Request'
import { render } from '/lib/enonic/react4xp'
import { assetUrl, getComponent, getContent, imageUrl } from '/lib/xp/portal'

export function macro (context) {
  const text = context.params.text
  const component = getComponent()

  const props = { text }

  return render(component, props, undefined, {})
}

blockquote.tsx

import React from 'react'

function BlockquotePart({ text }: { text: string }) {
  return <div>{text}</div>
}

export default (props) => <BlockquotePart {...props} />

blockquote.xml

<macro>
  <display-name>Blockquote</display-name>
  <description>Blockquote macro</description>

  <form>
    <input name="text" type="TextArea">
      <label>Text</label>
    </input>
  </form>
</macro>

Hi. Could I get some help on this?
I’ve got a much simpler example of the problem, but without the error happening in the frontend.
This is the code for the macro (it just returns a hardcoded html, im doing this to make the test as simple as possible):
quote.ts

import { render } from '/lib/enonic/react4xp'
import { assetUrl, getComponent, getContent, imageUrl } from '/lib/xp/portal'

export function macro(context) {
    const component = getComponent()

    const props = {}

    return render(component, props, undefined, {})
}

quote.tsx

import React from 'react'

function QuoteMacro() {
  return <div>testing macro</div>
}

export default (props) => <QuoteMacro {...props} />

quote.xml

<macro>
  <display-name>Quote</display-name>
  <description>Quote macro</description>

  <form>
    <input name="quote" type="TextArea">
      <label>Quote</label>
    </input>
    <input name="customerName" type="TextLine">
      <label>Customer Name</label>
    </input>
    <input name="customerTitle" type="TextLine">
      <label>Customer Title</label>
    </input>
    <input name="customerImage" type="ImageSelector">
      <label>Customer Image</label>
    </input>
  </form>
</macro>

This is a part that I created to test the macro, with only a htmlarea input and processing the text in the controller:
partTest.ts

import { PartComponent } from '@enonic-types/core'
import { render } from '/lib/enonic/react4xp'
import { getComponent, processHtml } from '/lib/xp/portal'

export function get(request) {
  const component: PartComponent = getComponent()
  const componentConfig = component?.config || {}
  const processedBody = componentConfig.htmlArea && processHtml({value: componentConfig.htmlArea as string})

  const props = {
    processedBody
  }

  return render(component, props, request)
}

partTest.tsx

import React from 'react'

function PartTest({ processedBody }) {
  return <div>{processedBody}</div>
}

export default (props) => <PartTest {...props} />

partTest.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<part>
    <display-name>Part Test</display-name>
    <form>
        <input type="HtmlArea" name="htmlArea">
            <label>HTML Area</label>
            <occurrences minimum="0" maximum="1" />
        </input>
    </form>
</part>

I am getting this output in the frontend:
image


How can I render the macro properly?

Why does the macro need to be react4xp? Why not just return normal html from the macro using for example thymeleaf or just a literal template string?

I’m not sure how well macros work in react4xp.

If this is important you should probably make a support case.

We have been working on a RichText react component and a tutorial:

It’s pure react though, based on GraphQL.
The idea was to be able to use it with react4xp and next.xp in the future, but we’re not there yet.

I see. So I’ve changed the macro to use simple .js and .html files.
The html is simply this:

<div>testing macro (quote macro)</div>
<div th:text="${quote}"></div>
<div th:text="${customerName}"></div>
<div th:text="${customerTitle}"></div>
<div th:text="${customerImage}"></div>

In the preview it works:



But when trying to render in the partTest aforementioned it’s still not working:

After looking and thinking a bit, my conclusion is that React4xp currently doesn’t support macros out of the box.

I’d have to fix this one:

Here are some workaround ideas:

  1. If it’s just macros in the same app, use dynamic require to render the macro in the part controller.

  2. If you want to implement the macros using react:
    2.1 Use regex on the htmlarea/proceccedHtml to figure out which macro and it’s attributes
    2.2 Implement one react component per macro you want to support, pass the attributes as props
    2.3 If unknown macro descriptors are encountered log a waring and just return null in react, or an error placeholder.

This is what the RichText component does: (but it expects data from guillotine)