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.
- Users can achieve the same using React Client Extension. Below is the reference for achieving this:
- 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 reactcreate_custom_element.sh
will create theclient-extension.yaml
file, an ‘src’ folder, and many other folders inside it. Replaceh5v7-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
, andFormTwo
. - 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-CX
is 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;
- It displays a greeting message along with the multi-step form.
- Initially,
FormOne
is rendered. Upon submission ofFormOne
,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:
- It includes fields for the user's name and email.
- The form data is managed using the
useState
hook. - When the form is submitted, the
submitted
the state is set totrue
, triggering the display ofFormTwo
.
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.
- It includes fields for a username and password.
- Similar to
FormOne
, the form data is managed using theuseState
hook. - When the form is submitted, the entered data is displayed as a JSON string in an alert box.