legacy-knowledge-base
公開されました Jun. 30, 2025

Multi-step form using react client extension

投稿者

SK Sahil Akhtar

knowledge-article-header-disclaimer-how-to

knowledge-article-header-disclaimer

legacy-article

learn-legacy-article-disclaimer-text

Issue

  • For a multi-step form, users typically use separate JSPs to handle redirection based on conditions. Now, there is a requirement to achieve the same functionality by using React.

Environment

  • Liferay DXP 2023.Q4

Resolution

NOTE: The following resolution requires customization and should only be implemented at the discretion of your team. Liferay Support will not be able to assist with designing or implementing customizations.

  1. Users can achieve the same using React Client Extension. Below is the reference for achieving this:
  2. Firstly, download the below sample react client extension:
    curl -Ls https://github.com/liferay/liferay-po
    rtal/raw/master/tools/create_custom_element.sh | bash -s h5v7-custom-element react
    create_custom_element.sh will create the client-extension.yaml file, an ‘src’ folder, and many other folders inside it. Replace h5v7-custom-element according to the use case.

Index.js

import React from 'react';
import { createRoot } from 'react-dom/client';
import api from './common/services/liferay/api';
import { Liferay } from './common/services/liferay/liferay';
import HelloBar from './routes/hello-bar/pages/HelloBar';
import HelloFoo from './routes/hello-foo/pages/HelloFoo';
import HelloWorld from './routes/hello-world/pages/HelloWorld';
import FormOne from './routes/hello-world/pages/FormOne';
import FormTwo from './routes/hello-world/pages/FormTwo';
import './common/styles/index.scss';

const App = ({ route }) => {
switch (route) {
case 'hello-bar':
return <HelloBar />;
case 'hello-foo':
return <HelloFoo />;
case 'form-one':
return <FormOne />;
case 'form-two':
return <FormTwo />;
default:
return <HelloWorld />;
}
};

class WebComponent extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
createRoot(this).render(
<App route={this.getAttribute('route')} />,
this
);

if (Liferay.ThemeDisplay.isSignedIn()) {
api('o/headless-admin-user/v1.0/my-user-account')
.then((response) => response.json())
.then((response) => {
if (response.givenName) {
const nameElements = document.getElementsByClassName('hello-world-name');

if (nameElements.length) {
nameElements[0].innerHTML = response.givenName;
}
}
});
}
}
}
const ELEMENT_ID = 'test-react-cx';

if (!customElements.get(ELEMENT_ID)) {
customElements.define(ELEMENT_ID, WebComponent);
}

This is the entry point of the React application.

  • It imports all necessary components, such as HelloWorld, FormOne, and FormTwo.
  • The App component is responsible for routing within the application, determining which component to display based on the provided route.
  • A custom HTML element test-react-CXis defined and used to render the React application within Liferay.
  • The connectedCallback method handles the initial rendering of the component and, if the user is signed in, fetches the user’s details to personalize the experience.

HelloWorld.js

import React from 'react';
import FormOne from './FormOne';
import FormTwo from './FormTwo';const HelloWorld = () => (
   <div className="hello-world">
       <h1>
           Hello <span className="hello-world-name">World</span>
       </h1>
       <FormOne/>
   </div>
);export default HelloWorld;
  1. It displays a greeting message along with the multi-step form.
  2. Initially, FormOne is rendered. Upon submission of FormOne, FormTwo replaces it.

FormOne.js

import React, { useState } from 'react';
import FormTwo from './FormTwo';

const FormOne = () => {
const [formData, setFormData] = useState({ name: '', email: '' });
const [submitted, setSubmitted] = useState(false);
const handleChange = (e) => {

setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
setSubmitted(true);
};

return (
<div>
{submitted ? (
<FormTwo />
) : (
<div>
<h2>Form One</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Name:</label>
<input type="text" name="name" value={formData.name} onChange={handleChange} required/>
</div>
<div>
<label>Email:</label>
<input type="email" name="email" value={formData.email} onChange={handleChange} required/>
</div>
<button type="submit">Submit</button>
</form></div>
)}
</div>
);
};
export default FormOne;

This component represents the first step in the multi-step form process:

  1. It includes fields for the user's name and email.
  2. The form data is managed using the useState hook.
  3. When the form is submitted, the submitted the state is set to true, triggering the display of FormTwo.

FormTwo.js

import React, { useState } from 'react';


const FormTwo = () => {
const [formData, setFormData] = useState({ username: '', password: '' });
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};

const handleSubmit = (e) => {
e.preventDefault();
alert(`Submitted data: ${JSON.stringify(formData)}`);
};

return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input type="text" name="username" value={formData.username} onChange={handleChange} />
</label>
<label>
Password:
<input type="password" name="password" value={formData.password} onChange={handleChange} />
</label>
<button type="submit">Submit Form Two</button>
</form>
);
};

export default FormTwo;

This component represents the second step in the form process.

  1. It includes fields for a username and password.
  2. Similar to FormOne, the form data is managed using the useState hook.
  3. When the form is submitted, the entered data is displayed as a JSON string in an alert box.

Additional Information

did-this-article-resolve-your-issue

legacy-knowledge-base