Adobe

Creating Your First Custom AEM Component Using React – Part 1

Lee Campbell 6njoebtarec Unsplash

Recently, I went through an article about integrating React JS and Angular JS with AEM. In this blog, I am going to show you how to create a custom component that includes a cq:dialog and one that does not include a cq:dialog. Before building the components, clone the repository, which is a sample project based on React JS.
First, we will deploy this project in AEM 6.5. Then, we will create one sample component called custom-heading.
The component structure should look like this.
1. Component node (<project root>/apps/my-aem-project/components/content/custom-heading)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:Component"
    jcr:title="Custom Heading"
    componentGroup="My AEM Project"/>

2. cq:dialog

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
    xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
    xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="Custom Heading"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/container">
        <items jcr:primaryType="nt:unstructured">
            <tabs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/tabs"
                maximized="{Boolean}true">
                <items jcr:primaryType="nt:unstructured">
                    <general
                        jcr:primaryType="nt:unstructured"
                        jcr:title="General"
                        sling:resourceType="granite/ui/components/coral/foundation/container"
                        margin="{Boolean}true">
                        <items jcr:primaryType="nt:unstructured">
                            <columns
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
                                margin="{Boolean}true">
                                <items jcr:primaryType="nt:unstructured">
                                    <column
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/coral/foundation/container">
                                        <items jcr:primaryType="nt:unstructured">
                                            <heading
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                fieldLabel="Heading"
                                                name="./heading"
                                                required="{Boolean}true"/>
                                            <heading-type
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                                fieldLabel="Heading Type"
                                                name="./headingType">
                                                <items jcr:primaryType="nt:unstructured">
                                                    <h1
                                                        jcr:primaryType="nt:unstructured"
                                                        text="H1"
                                                        value="h1"/>
                                                    <h2
                                                        jcr:primaryType="nt:unstructured"
                                                        text="H2"
                                                        value="h2"/>
                                                    <h3
                                                        jcr:primaryType="nt:unstructured"
                                                        text="H3"
                                                        value="h3"/>
                                                </items>
                                            </heading-type>
                                            <heading-color
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                                fieldLabel="Heading Color"
                                                name="./headingColor">
                                                <items jcr:primaryType="nt:unstructured">
                                                    <red
                                                        jcr:primaryType="nt:unstructured"
                                                        text="Red"
                                                        value="red-color"/>
                                                    <green
                                                        jcr:primaryType="nt:unstructured"
                                                        text="Green"
                                                        value="green-color"/>
                                                    <blue
                                                        jcr:primaryType="nt:unstructured"
                                                        text="Blue"
                                                        value="blue-color"/>
                                                </items>
                                            </heading-color>
                                        </items>
                                    </column>
                                </items>
                            </columns>
                        </items>
                    </general>
                </items>
            </tabs>
        </items>
    </content>
</jcr:root>

Now the question is why have I not created custom-heading.html? For a React-based component, we are not going to create any custom-heading.html. Also, we are not going to use HTL/Sightly to render logic. Surprising, right? We will render the component HTML using React. Therefore, I have created a React component called CustomHeading.
In the project you’ve cloned, create the CustomHeading (<project root>/react-app/src/components/CustomHeading) folder and create the following two files.
The react component structure should look like this.
1. CustomHeading.js

import React, {Component} from 'react';
import {MapTo} from '@adobe/cq-react-editable-components';
require('./CustomHeading.scss');
const CustomHeadingEditConfig = {
    emptyLabel: 'Custom Heading',
    isEmpty: function(props) {
        return !props || props.heading.trim().length < 1;
    }
};
export default class CustomHeading extends Component {
    render() {
            return (<div className="heading"> First Component </div>);
    }
}
MapTo('my-aem-project/components/content/custom-heading')(CustomHeading, CustomHeadingEditConfig);

2. CustomHeading.scss

.red-color{
  color: #fc0b03;
}
.green-color{
color : #39fc03;
}
.blue-color{
color : #2403fc;
}
Adobe - Content for Everyone
Content for Everyone

Companies that can quickly and consistently meet the demands of consumers are thriving in an era of infinite content. Learn about how to build fluid experiences for your omnichannel customers.

Get the Guide

Now, the question is still how do you read the authored value in a React component? For this, we need a Sling Model exporter. I have created one Sling Model exporter class.

The Sling Model will look like this.
CustomHeadingModel.java

package com.surajkamdi.core.models;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import javax.annotation.Nonnull;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = SlingHttpServletRequest.class,
    resourceType = CustomHeadingModel.RESOURCE_TYPE,
    adapters = {CustomHeadingModel.class, ComponentExporter.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class CustomHeadingModel implements ComponentExporter {
  protected static final String RESOURCE_TYPE = "my-aem-project/components/content/custom-heading";
  @ValueMapValue(name = "heading")
  private String heading;
  @ValueMapValue(name = "headingType")
  private String headingType;
  @ValueMapValue(name = "headingColor")
  private String headingColor;
  public String getHeading() {
    return heading;
  }
  public String getHeadingType() {
    return headingType;
  }
  public String getHeadingColor() {
    return headingColor;
  }
  @Nonnull
  @Override
  public String getExportedType() {
    return RESOURCE_TYPE;
  }
}

Run the build and deploy using the following Maven commands:

mvn clean install -PautoInstallPackage -Padobe-public

Now let’s add this component into the page and do some authoring, then get the resource path of component. Open a new tab in the browser window and paste the following URL constructed using resource path in the following format:
http://localhost:4502/<content_root_path>/custom_heading.model.json 

CustomheadercomponentjsonYou will notice authored values are also present here in the above structure. This is the output rendered by the above Sling Model exporter class.
In order to add this component into the page created in AEM, we need to import the component inside file called <project root>/react-app/src/ Index.js. If you are using the above-mentioned repository structure, then you need to include a component path inside MappedComponents.js. Otherwise, you will not able to add a component into the page.
MappedComponets.js

require('./CustomHeading/CustomHeading');

Finally, the last step is to read the above JSON in the React component and render the logic to achieve component functionality.
CustomHeading.js

import React, {Component} from 'react';
import {MapTo} from '@adobe/cq-react-editable-components';
require('./CustomHeading.scss');
const CustomHeadingEditConfig = {
    emptyLabel: 'Custom Heading',
    isEmpty: function(props) {
        return !props.heading || props.heading.trim().length < 1;
    }
};
export default class CustomHeading extends Component {
    render() {
      let headingElement = this.props.headingType ? React.createElement(this.props.headingType, {className: this.props.headingColor},this.props.heading) : '';
      return (<div className="heading"> {headingElement} </div>);
    }
}
MapTo('my-aem-project/components/content/custom-heading')(CustomHeading, CustomHeadingEditConfig);

Now, deploy your code using Maven commands.

mvn clean install -PautoInstallPackage -Padobe-public

Custom Aem React Component Surajkamdi
I hope this blog post was helpful. In the next blog, I will create a component without using cq:dialog.
If you are still confused about working with the React component, visit here or comment below.

About the Author

Suraj is an Adobe MVP and an active member of the Adobe Experience Manager Community. He is an Adobe Certified - Adobe Experience Manager Sites Developer & DevOps Engineer. He also holds the Scum alliance Certification in Scrum Master (CSM®) and Scrum Developer (CSD®).

More from this Author

Thoughts on “Creating Your First Custom AEM Component Using React – Part 1”

  1. hi really great articles. how can i make another component lets say, i want to create a new test component for example bassed on CustomHeading component. i do these step but my component didnt show in crxde or in the editor.html. so where im wrong? i already create testingmodel.java

  2. Hello,
    Could you please share the git repository URL, So that I can take a look at it. else you can open a question on StackOverflow and include all the details and share a link with me.
    I am happy to help you…
    Thanks & Regards,
    Suraj Kamdi

  3. Hi Suraj, I have a great doubt. what if the AEM version is below 6.4 eg: 6.1/6.2 then how do you integrate react with AEM?

  4. Hugo Manuel Moreira Meireles

    Hi Suraj,
    Nice article.
    it is possible to a create react component(authoring) with 2 children core component?
    Example:
    banner component – have the image component where I can change it in authoring mode and text component with the same behavior.

  5. Suraj Kamdi Post author

    Hello Satish,
    First of all, You need to create one Sling model for your component. I have justified my answer using the following example.

    @Model(adaptables = SlingHttpServletRequest.class,
    resourceType = CustomSlingModel.RESOURCE_TYPE,
    adapters = {CustomSlingModel.class, ComponentExporter.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    @Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
    public class CustomSlingModelimplements ComponentExporter {
    // Read the childResouce for multifield resource using @ChildResource private Resource multifieldResourceName;
    // and then create a ArrayList of POJO class for multifield data items by iterating childNodes of multifieldResource.
    List multifiledList;
    }

    Once you are done with the Sling model then provide the exact resource type (as same as provided in sling model) to your react component using MapTo().
    After that consume the multifiledList values from the generated model.json file and render your logic for your multifield component using map() function

    See this link for more details.

    Please let me know if you need any help. I am happy to help 🙂

    PS. This solution applies only to react component.
    Thanks & Regards,
    Suraj Kamdi

  6. hi Suraj,

    Could you please provide and example with code for handling the multi-fields using react components?

  7. Hi Suraj

    Really great article. I am beginner of AEM and React your article is very clear and crisp that I created my own component following the steps you have explained.

    May I know what is the aem archetype you have used for building the project.

    Thanks in advance

    Elena

  8. hi Suraj,

    Could you please show an example to render a multi-field properties in model.json using json extractor?

  9. Suraj Kamdi Post author

    Hello Kumar,
    See my above comment

    See this example for rendering the multifield of List Component.

    Please let me know if you need any help. I am happy to help 🙂
    Thanks & Regards,
    Suraj Kamdi

  10. hi Suraj,

    I am asking here about the json extraction of multifield component not the react code.

  11. Suraj Kamdi Post author

    Hello Kumar,
    Here is the example to create a sling model to extract the JSON from multifield Items.
    URL: https://gist.github.com/Surajkamdi/6d51779d62278b56a747e04532f39036
    I hope this will help to sort out your queries related to the JSON extraction of the multifield component.

    Please let me know if you need more help with this… I am Happy to Help 🙂

    Please connect me on LinkedIn: https://www.linkedin.com/in/suraj-kamdi-95a80194/

    Thanks & Regards,
    Suraj Kamdi

  12. SHASHANK ARCOT

    Good Article.
    I think u meant import-components.js instead of MappedComponents.js

  13. Suraj Kamdi Post author

    Thank You, Shashank 🙂
    I have created separate MappedComponents.js to include all the components into single file. Instead, you can also import your components inside App.js file or index.js file.

    Thanks & Regards,
    Suraj Kamdi

  14. kaushik datta

    Hi Suraj ,

    Nice article !
    Cloned your project and it worked perfect.
    Now I want to create another component consisting of two dialog field one for path and text.
    My react component :
    import React, {Component, Fragment} from ‘react’;
    import {MapTo} from ‘@adobe/cq-react-editable-components’;
    require(‘./TextOverImage.scss’);

    const TextOverImageEditConfig = {
    emptyLabel: ‘Text Over Image’,
    isEmpty: function(props) {
    return !props.imgPath || props.imgPath.trim().length < 1 || props.imgText.trim().length < 1;
    }
    }
    export default class TextOverImage extends Component{
    render(){
    return (

    Nature
    Hey it works !!

    )

    }
    }

    MapTo(‘my-aem-project/components/content/textOverImage’)(TextOverImage, TextOverImageEditConfig);

    My model :

    package com.surajkamdi.core.models;

    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.models.annotations.DefaultInjectionStrategy;
    import org.apache.sling.models.annotations.Exporter;
    import org.apache.sling.models.annotations.Model;
    import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
    import javax.annotation.Nonnull;

    import com.adobe.cq.export.json.ComponentExporter;
    import com.adobe.cq.export.json.ExporterConstants;

    @Model(adaptables = SlingHttpServletRequest.class, resourceType = CustomHeadingModel.RESOURCE_TYPE, adapters = {
    TextOverImageModel.class,
    ComponentExporter.class }, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    @Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
    public class TextOverImageModel implements ComponentExporter {

    protected static final String RESOURCE_TYPE = “my-aem-project/components/content/textOverImage”;

    @ValueMapValue(name = “imgPath”)
    private String imgPath;

    @ValueMapValue(name = “imgText”)
    private String imgText;

    public String getImgPath() {
    return imgPath;
    }

    public String getImgText() {
    return imgText;
    }
    @Nonnull
    @Override
    public String getExportedType() {
    // TODO Auto-generated method stub
    return RESOURCE_TYPE;
    }

    }

    dialog :

    My component is not showing in the page. Will you please help me out.

  15. Suraj Kamdi Post author

    Hello Kaushik,
    Could you please share the git repository URL, So that I can take a look at it.
    I am happy to help 🙂 …

    Thanks & Regards,
    Suraj Kamdi

  16. Suraj Kamdi Post author

    Hello Kaushik
    It looks like there is a problem with TextOverImage.js. Some of the properties are getting null values.
    Open the browser console see to error.

    Use TextOverImageEditConfig to check null values.

    Thanks & Regards,
    Suraj

  17. KAUSHIK DATTA

    Hi Suraj ,

    What is the use of EditConfig just as used in this example CustomHeadingEditConfig ?
    Here I can see you used props.heading as heading is one of the dialog field , so how you can choose which one to select among all dialog fields or we have to put all of them. Will you please provide an example or just explain the use of it.

    Regards,
    Kaushik

  18. Suraj Kamdi Post author

    Hello Kaushik,
    The use of EditConfig is to show the component placeholder when the component is not authored. it means that the values should not be null in order to render the component HTML on the Page else component will not display on the page after adding into parsys. As a result, Components JS will throw an error and it will not load on the page properly. You can use the browser console to debug your components js.

    You can compare one of the important/required properties which will never return null value in model.json.

    Please let me know if you need more help with this. I am happy to help 🙂
    Connect me on Linkedin: https://www.linkedin.com/in/suraj-kamdi-95a80194/

    Thanks & Regards,
    Suraj Kamdi

  19. hi suraj/all. i did not see parsys to add components from structure template.usually parsys is visble to add component on template…can u plz help on this

  20. Suraj Kamdi Post author

    Hello Rahul,
    I am not able to reproduce this issue in my local. Can you please specify which AEM version you are using?
    I am happy to help.

    Thanks & Regards,
    Suraj Kamdi

  21. Suraj Kamdi Post author

    Hello Rahul,
    Can You please elaborate on it. I mean what is your doubt?

  22. HI SURAJ, FIRST I WOULD LIKE TO THANKS FOR YOU For THIS GREAT LEARING AND ARTICLE..my concern is that WHEN WE ARE OPENING EDITABLE TEMPLE IN STRUCTURE MODE GENRALLY WE HAVE SOME ALLOWED COMPONENT WITH DEFined POLICY IN LAYOUT CONTAINER..there parsys also available there to drag drop any other component on tempolate level ..BUT IN YOUR EXample i did not see parsys in temple structre mode can you help how we can make visible parsys if i want to add some component in templte structe mode..i am using 6.5

  23. HI SURAJ, FIRST I WOULD LIKE TO THANKS FOR YOU For THIS GREAT LEARING AND ARTICLE..my concern is that WHEN WE ARE OPENING EDITABLE TEMPLE IN STRUCTURE MODE GENRALLY WE HAVE SOME ALLOWED COMPONENT WITH DEFined POLICY IN LAYOUT CONTAINER..there parsys also available there to drag drop any other component on tempolate level ..BUT IN YOUR EXample i did not see parsys in temple structre mode can you help how we can make visible parsys if i want to add some component in templte structe mode..i am using 6.5………………………………..suraj, can you please add any real time proiject article when you get free so that me and my college will get some learning

  24. thanks suraj, you atre doing great job to help the people by posting these kind of blog…much appreciated….please reply if you get the issue that comes due to responsive grid..when i am opeing the temple in structre mode it is not showing responsive grid or parsyss there..and also in the path – /content/my-aem-project/blog-page/jcr:content/root –/custom_heading – here responsivegrid node should be there i think but it is not there..just for your help………..also suraj..i have one query please..to make our component responsive ar ewe using bootstarp in real time projects or setting breakpoint bfromlayout mode of template is sufficient

  25. Suraj Kamdi Post author

    Thanks, Rahul
    I will Look into the issue. Maybe there is a problem with the template config.
    To make responsive you can use Bootstrap, well that’s up to you.
    You can also use AEM Responsive grid classes or you can write your own custom CSS.

  26. Hi Suraj,

    First of all, thank you for this awesome guide!
    I have a question – Is it necessary to create a seperate sling model exporter class for every component we make? Would it be possible to create a generalised exporter class so that it recognizes any custom component’s properties? It feels like a lot of repeated code to create an exporter class for every component we create.

    Thanks!
    Jacky

  27. Suraj Kamdi Post author

    Thanks Jacky :-),
    Yes, we do need the Sling Model Exporter class for each component.
    you can also create a generalized exporter class and extend this to the Component model class.

    Please let me know if you need any help with this.
    I am happy to help 🙂
    Thanks & Regards,
    Suraj Kamdi

  28. Hi Suraj,

    Thanks for the above article it is very helpful but i am facing one issue i have fallowed same as per your article but i am facing issue like i am not able store the values on the page, i am editing the component giving some text in the heading filed but is not showing on the page could you pls help on this.

  29. Suraj Kamdi Post author

    Thanks, Hemanth
    Please provide some git-repo or any screenshots so that it will help me to understand your issue.
    I am happy to help.

  30. Hi Suraj,

    Could you please provide a blog link where you have create react component mapping to AEM component which do not have cq:dialog. I have a requirement to create a HTML form in component (Which do not have any dialog), on submitting the form the values should be posted to rest api through react component.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to the Weekly Blog Digest:

Sign Up
Categories