Recently I took on the challenge of building a React app that utilizes Typescript & Material UI by using the Liferay-JS Yeoman generator. This approach was taken as an alternative to using Create React App & adapting it to Liferay. The benefit is there are no extra steps needed to gain access to the Liferay Params (configuration, contextPath, portletElementId, & portletNamespace) as there would be in an CRA adaptation.

Here are the steps I have found that, at the time of writing this blog, are working for me…

**NOTE – You must use NPM & not Yarn. Using Yarn causes errors when bundling MUI components. Also, this is written using DXP 7.3**

1. Create a React App using the yeoman liferay-js generator. If you have not globally installed yeoman & the generator-liferay-js npm packages, follow the links & instructions below.

2. Once the App has been created install the following dependencies:

  • Dev dependencies
    • @babel/preset-typescript
    • liferay-npm-bundler-plugin-inject-imports-dependencies
npm i -D @babel/preset-typescript liferay-npm-bundler-plugin-inject-imports-dependencies
  • Standard dependencies
    • typescript
    • @types/node 
    • @types/react 
    • @types/react-dom 
    • @types/jest (optional for testing)
    • @types/material-ui
    • @material-ui/core
    • @material-ui/icons
    • @material-ui/styles
    • @material-ui/lab (optional)
npm i typescript @types/node @types/react @types/react-dom @types/jest @types/material-ui @material-ui/core @material-ui/icons @material-ui/styles @material-ui/lab

3. Run ‘tsc –init’ in your root folder to create a tsconfig.json. The following tsconfig.json works at the time of writing this:

    "compilerOptions": {
      "target": "ES5",
      "module": "ESNext",
      "lib": [
      "allowJs": true,
      "checkJs": false,
      "jsx": "react-jsx",
      "declaration": true,
      "noEmit": true,
      "isolatedModules": true,
      "strict": true, 
      "noImplicitAny": true,
      "strictNullChecks": true,
      "noImplicitThis": true,
      "noFallthroughCasesInSwitch": true,
      "moduleResolution": "node",
      "allowSyntheticDefaultImports": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "resolveJsonModule": true
    "include": [
    "exclude": [

4. Update your .babelrc to the following:

    "presets": [
                "isTSX": true,
                "allExtensions": true

5. Update your .npmbundlerrc to the following. This is so Material UI components will work w/ the Liferay generated bundle:

    "create-jar": {
        "output-dir": "dist",
        "features": {
            "js-extender": true,
            "web-context": "/exercise1.1",
            "localization": "features/localization/Language",
            "configuration": "features/configuration.json"
    "dump-report": true,
    "*": {
        "plugins": ["inject-imports-dependencies"]

6. Update the build script & add the check-types script to your package.json. Adding ‘check types’ will make sure there are no TS errors before Babel complies. If there are errors the compile step will not happen:

"build": "npm run check-types && npx babel src -d build src --extensions '.ts,.tsx' && npm run copy-assets && liferay-npm-bundler",
 "check-types": "tsc",

7. You should now be able to use Typescript & Material UI in your Liferay generated React App. Rename your index & AppComonent files from .js to .tsx. You will have to create types for the Liferay generated props. As a shortcut here is my index.tsx file:

import React from 'react';
import ReactDOM from 'react-dom';
import AppComponent from './AppComponent';
interface IProps {
    portletNamespace: string;
    contextPath: string;
    portletElementId: string;
    configuration: any;
 * This is the main entry point of the portlet.
 * See for the most recent 
 * information on the signature of this function.
 * @param  {Object} params a hash with values of interest to the portlet
 * @return {void}
export default function main({portletNamespace, contextPath, portletElementId, configuration}: IProps){    

And AppComponent.tsx:

import React from 'react';
declare global {
    interface Window {
        Liferay: any;
interface IProps {
    portletNamespace: string;
    contextPath: string;
    portletElementId: string;
    configuration: any;
const Liferay: any = window.Liferay;
const AppComponent: React.FC<IProps> = (props: IProps) => {
    return (
                <span className="tag">{Liferay.Language.get('portlet-namespace')}:</span> 
                <span className="value">{props.portletNamespace}</span>
                <span className="tag">{Liferay.Language.get('context-path')}:</span>
                <span className="value">{props.contextPath}</span>
                <span className="tag">{Liferay.Language.get('portlet-element-id')}:</span>
                <span className="value">{props.portletElementId}</span>
                <span className="tag">{Liferay.Language.get('configuration')}:</span>
                <span className="value pre">{JSON.stringify(props.configuration, null, 2)}</span>
export default AppComponent;

8. Now deploy the app to your local Liferay & if everything was setup right you should see something akin to the following (names will be different) after adding your new app to a content page:

Image Building a Liferay JS Generated React App deploy local Liferay

9. At this point, you can start building your app & adding Material UI components. As stated in the note at the top make sure you are using npm & not yarn. Somewhere in your node_modules yarn installs a second instance of React which causes an Invariant Violation: Invalid hook call error after compiling the code & running in Liferay.


Babel preset typescript:

Babelrc config for TS:

Creating a ‘check-types’ script & other Babel/TS info:

How to implement material-ui on Liferay:

Material UI w/ TS:

Use NPM & not Yarn:

Share This