This article describes DXP infrastructure to render React components from the server side perspective. This includes taglibs as well as low-level services.
- OSGi bundle: com.liferay.portal.template.react.renderer.api
- Interface name: com.liferay.portal.template.react.renderer.ReactRenderer
- Service type:
singleton
This is the low-level service to render React components.
⚠ Note that it only renders React components, not any React code, so you must provide a source which exports a React component to render. ⚠
It has one method:
public void renderReact(
ComponentDescriptor componentDescriptor, Map<String, Object> data,
HttpServletRequest httpServletRequest, Writer writer)
throws IOException;
which receives:
- A ComponentDescriptor describing the source of the React component to render. More on this in the next section.
- A
Map
containing arbitrary data that will be injected to the React component as initial properties. Note that the data inside the map must be compatible with JavaScript, so you cannot inject any Java object you like, only primitive types,Map
s,Array
s, ... that can be serialized to JSON. - The current
HttpServletRequest
for which the component is being rendered. - A
Writer
where the rendered HTML and JavaScript are written to.
Note that parameter 4 is not intended to be used to write the rendered HTML and JavaScript to any place you like but to pass the
PrintWriter
from theHttpServletResponse
or theJspWriter
from thePageContext
you have available when invoking the method.
Also, take into account that, if you don't render the JavaScript inline (see option
positionInline
of ComponentDescriptor), the JavaScript will be written to the bottom of the currentHttpServletResponse
, while the HTML will be inlined into the passedWriter
.
ComponentDescriptor instances are value objects that describe what needs to be rendered. A ComponentDescriptor
may have up to five fields (of which only the first one is mandatory):
module
: the name of the AMD module that exports the React component to be rendered. Note that you need to provide the canonical name of the module (including its version number, like[email protected]/js/Foo
). To avoid hardcoding any version number you may use the instance of the NPMResolver service tied to the OSGi bundle containing the AMD module to be rendered, or alternatively, call any of the NPMResolverUtil methods (f.e.:NPMResolverUtil.resolveModuleName(this.class, 'js/Foo.')
).componentId
: thecomponentId
that will be given to the component's JavaScript as an initial property. Note that this field is only used if thedata
map passed torenderReact
does NOT contain acomponentId
property.dependencies
: this is a reserved parameter which can be used in the future to declare additional dependencies. For now, it has no effect.positionInline
: a flag to render the JavaScript directly in the current position of the HTML response or defer it to the bottom of the page. By defaultfalse
.propsTransformer
: optional name of an AMD module exporting a function to transform the properties before they are passed to the component. It has to follow the same rules as the module attribute (you must include the resolved version number).
Say you run the following code:
ComponentDescriptor componentDescriptor =
new ComponentDescriptor(
"[email protected]/component/button", "myButton", null, false,
"[email protected]/propsTransformer");
Map<String, Object> data = new HashMap<>();
data.put("color", "red");
reactRenderer.renderReact(
componentDescriptor, data, httpServletRequest,
httpServletResponse.getWriter());
This will cause HTML like this to be rendered in the current position of the HttpServletResponse
:
<div id="XJAZ"></div>
(where XJAZ
is a random placeholder id).
Note that the React component will be mounted in the parent element of this
<div>
as per therender
method in the helper render.es module.
And JavaScript like this to be appended at the end of the page:
Liferay.Loader.require(
'[email protected]/render.es',
'[email protected]/component/button',
'[email protected]/propsTransformer'
function (render, renderFunction, propsTransformer) {
render(renderFunction, propsTranformer({'color': 'red'}));
}
);
If you are curious about what [email protected]/render.es
contains, you can see its content in liferay-portal's source code. It is simply a helper to connect the generated JavaScript and the React component but, in general, it's a level of indirection to be able to add any extra infrastructure code we may need in the future.
- OSGi bundle: com.liferay.frontend.taglib.react
- Taglib URI: http://liferay.com/tld/react
- Tag: react:component
This tag is a wrapper for the low-level React renderer service. It leverages OSGi service com.liferay.portal.template.react.renderer.ReactRenderer
to render the HTML and JavaScript.
The tag has five attributes (all optional but the module
one):
componentId
: the component identifier. It can be used to reference the React component withLiferay.component(componentId)
.data
: deprecated, useprops
(described below) instead.module
: the name of the AMD module which exports the React component. Note that you don't need to provide the package name, just the module name and it will be resolved according to the providedservletContext
'spackage.json
file using thecom.liferay.frontend.js.loader.modules.extender.npm.NPMResolver
service.props
: aMap
with properties to be passed to the React component. See thedata
parameter in therenderReact
method of the React renderer service.servletContext
: theServletContext
that will be used to obtain thecom.liferay.frontend.js.loader.modules.extender.npm.NPMResolver
to resolve themodule
name. If not given, theservletContext
of the JSP invoking<react:component>
will be used.
<react:component
componentId="myButton"
module="component/button"
props="<%=
HashMapBuilder.<String, Object>put(
"color", "red"
).build()
%>"
servletContext="<%= application %>"
/>
- OSGi bundle: com.liferay.frontend.taglib.clay
- Taglib URI: http://liferay.com/tld/clay
- Tags: liferay-clay:...
The liferay-clay
taglib provides several tags to render Clay components (for example: <liferay-clay:alert>
, <liferay-clay:button>
, ...). This is indirectly related to React because Clay components are implemented as React components.
Each tag has its own attributes, which are modeled according to the visual component the tag is rendering. See the documentation of each tag to learn about its attributes. A good place to do start is the taglib's TLD file.
All Clay tags are supposed to extend from com.liferay.frontend.taglib.clay.internal.servlet.taglib.BaseContainerTag
which, in turn, leverages com.liferay.portal.template.react.renderer.ReactRenderer
.
Then, at this low-level, all Clay components are created with a ComponentDescriptor containing:
- A
moduleName
which is defined by each tag, and corresponds to the JavaScript module that exports the React component. - A
data
map which is filled with the attributes of the tag, likecssClass
,defaultEventHandler
,id
, etc...
<clay:label
displayType="warning"
label="pending"
/>
<clay:alert
message="you-do-not-belong-to-an-organization"
/>