Rich text
Contents
In this chapter you’ll learn how to render rich text content.
Background
Enonic boasts a rich text input type. Unlike basic text fields, rich-text may contain more complex concepts such as tables, images, links and even custom components (aka macros). Each of these typically need custom processing during rendering. For instance to ensure contextually correct URLs, image sizes etc.
Sample content
The Bio
field in the person content type is exactly what we’re looking for:

Rich text rendering
We’ll be using our existing Person component, and add support for the Bio
field.
Follow the steps below:
-
Update the Person processor file to include the
bio
prop:/src/main/resources/react4xp/components/content/PersonProcessor.tsimport {processHtml} from '/lib/enonic/react4xp'; import {get as getContentByKey} from '/lib/xp/content'; import {imageUrl} from '/lib/xp/portal'; import {toArray} from "/react4xp/utils/arrayUtils"; import {PageDescriptor} from '@enonic-types/core'; import type {Content} from '@enonic-types/lib-content'; import type {ComponentProcessor} from '@enonic-types/lib-react4xp/DataFetcher'; function fetchAdditionalPhotos(photosIds) { return photosIds.map(photoId => { if (photoId) { const photoContent = getContentByKey<Content>({key: photoId}); return { _id: photoContent._id, title: photoContent.displayName, imageUrl: imageUrl({id: photoContent._id, scale: 'block(175, 175)'}) }; } }); } export const personProcessor: ComponentProcessor<PageDescriptor> = (params) => { const photos: string[] = toArray<string>(params.content.data.photos as string | string[]) const firstPhotoId = photos[0] || ''; const remainingPhotoIds = photos.slice(1); let firstPhoto = null; if (firstPhotoId) { const {_id, displayName} = getContentByKey<Content>({key: firstPhotoId}); firstPhoto = { _id, title: displayName, imageUrl: imageUrl({id: _id, scale: 'block(1200, 675)'}) } } const extraPhotos = fetchAdditionalPhotos(remainingPhotoIds); return { displayName: `${params.content.displayName}`, photo: firstPhoto, birthDate: params.content.data.dateofbirth, restPhotos: extraPhotos, bio: processHtml({ value: params.content.data.bio as string, imageWidths: [200, 400, 800], }) }; };
The ProcessHtml function (part of React4XP) outputs a format that the RichText component can use, as you will see below:
-
Update the Person component with the following content:
/src/main/resources/react4xp/components/content/Person.tsximport {componentRegistry} from '/react4xp/componentRegistry'; import {RichText, type ComponentProps} from '@enonic/react-components'; import React from 'react' import styles from './Person.module.css'; export const Person = (props: ComponentProps) => { const {displayName, photo, restPhotos, bio, birthDate} = props.data as any; return ( <div className={styles.person}> <h1>{displayName}</h1> <p>{birthDate}</p> <div> { photo ? ( <> <div className={styles.photos}> <img src={photo.imageUrl} title={photo.title} alt={photo.title} height={675} width={1200} loading="eager" /> </div> </> ) : ( <p>No photo available</p> ) } {restPhotos && restPhotos.length > 0 && ( <> <h2>Photos</h2> <div className={styles.photoContainer}> <div className={styles.photoGrid}> <div className={styles.photoScroll}> {restPhotos.map((photo, index) => ( <img key={index} src={photo.imageUrl} title={photo.title} alt={photo.title} height={175} width={175} /> ))} </div> </div> </div> </> )} </div> {bio && ( <> <h2>Bio</h2> <div className={styles.richText}> <RichText data={bio} componentRegistry={componentRegistry} loading="lazy" /> </div> </> )} </div> ) }
Et voilà! And just like that we have a fully functional rich text rendering. The page should now look something like this:

If you have a sharp eye, you might notice there is something missing, the factbox is not rendering properly!
Macros
The HMDB dataset defines a Fact-Box
macro, which editors may use inside their rich-text input.
-
Add the following files to your app:
/src/main/resources/react4xp/components/macro/FactBox.tsximport {MacroComponentParams} from '@enonic/react-components'; import React from 'react'; import styles from './FactBox.module.css'; export const Factbox = ({config, children}: MacroComponentParams) => { return ( <ins className={styles.factbox}> <i>💬</i> <strong className={styles.header}>{config.header ? config.header as string : "Fact Box"}</strong> <div className={styles.bodyContent}> {children}</div> </ins> ); };
/src/main/resources/react4xp/components/macro/FactBox.module.css.factbox { display: block; text-decoration: none; border-radius: 8px; clear: both; padding: 10px 20px; max-width: fit-content; margin: 1rem; position: relative; background-color: #0b0425; i { font-style: normal; } } .factbox .header { font-size: 20px; line-height: 24px; margin: 0 0 0 1rem; }
-
Register the component
/src/main/resources/react4xp/componentRegistry.tsximport {ComponentRegistry} from '@enonic/react-components'; import {Person} from './components/content/Person'; import {Hello} from './components/hello/Hello'; import {Factbox} from './components/macro/FactBox'; export const componentRegistry = new ComponentRegistry(); componentRegistry.addContentType('portal:site', {View: Hello}); componentRegistry.addContentType('com.enonic.app.hmdb:person', {View: Person}); componentRegistry.addMacro('factbox', {View: Factbox});
Factbox complete!
You should now see the Factbox rendering properly

No dataProcessor?
What happened to the FactBox dataProcessor? Macros automatically get configuration values passed to the React component via the children
field.
To create a macro dataProcessor, register it using .addMacro instead of .addPage in your dataFetcher. |
Sweet! next up are pages.