The article for Alfresco developers that describes how to inject React and Redux into custom Alfresco Share page. It is a step by step guide which covers requirements for using React in Alfresco Share, a description of how to compile jsx files and how to define React components via Dojo
1. Add surf-module to your Alfresco Share add-on
You can introduce with extension modules by the link https://docs.alfresco.com/6.0/concepts/dev-extensions-share-surf-extension-modules-introduction.html (as for example, path to the file with extension can be ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/site-data/extensions/share-reactsample-customization.xml)
Code module snippet below:
FlexSolution-ReactJS-lib
1.0
true
2. Download required React libraries
You must download the following libraries:
- react.js
- redux.js
- react-redux.js
- react-dom.js
You can find libs on this site https://cdnjs.com/libraries
Then put files into ${path_to_your_share_module}/src/main/resources/web/js/lib folder, also create an empty folder called react-components there (we will use it later for our custom .jsx files).
3. Implement your business logic in jsx files
By way of example, we have implemented pretty straightforward logic which allows to watch the list of portfolio items and search by name. Copy content of the files bellow into ${path_to_your_share_module}/src/main/resources/web/js/lib/react-components folder or implement you own business logic based on these examples.
- Form.jsx:
const Form = (React,
store,
Articles,
Search) => {
return {
action(type, value, selectedName) {
return { type: type, [selectedName]: value};
},
onSearch(searchTerm) {
store.dispatch(this.action("SEARCH_SET", searchTerm, 'searchTerm'));
},
filterArticle(searchTerm, articles) {
let includes = articles.filter(article => article.title.toLowerCase().includes(searchTerm.toLowerCase()));
if (searchTerm === '' && includes.length === 0 ) {
includes = articles;
}
store.dispatch(this.action("UPDATE_ARTICLE_SET", includes, 'articles'));
return includes;
},
render() {
const Form = this.props;
return (
this.onSearch(e)}>
Search
);
}
}
};
define([
'dojo/_base/declare',
'react',
"redux",
"react-redux",
"react-components/Store",
"react-components/article/Articles",
"react-components/search/Search",
], function (declare,
React,
Redux,
ReactRedux,
Store,
Articles,
Search) {
const mapStateToProps = state => ({
initArticles: state.articlesState.initArticles,
searchTerm: state.searchState.searchTerm,
});
let store = Store.prototype;
let FormDeclared = declare(
"Form",
[React.Component],
Form(React,
store,
Articles,
Search));
return ReactRedux.connect(mapStateToProps)(FormDeclared)
});
- Reducer.jsx
define([
'dojo/_base/declare',
"redux",
'react-components/article/ArticleReducer',
'react-components/search/SearchReducer'
], function (declare,
redux,
ArticleReducer,
SearchReducer) {
return declare("Reducer", null, {
rootReducer: function rootReducer() {
let articleReducer = ArticleReducer.prototype.articleReducer;
let searchReducer = SearchReducer.prototype.searchReducer;
return redux.combineReducers({
articlesState: articleReducer,
searchState: searchReducer
});
}
});
});
- Runner.jsx
require([
`react`,
`react-dom`,
"redux",
"react-redux",
'react-components/Form',
'react-components/Store'
], function (React, ReactDOM, Redux, ReactRedux, Form, Store) {
let store = Store.prototype;
ReactDOM.render(
,
document.getElementById('reactRender')
);
});
- Store.jsx
define([
'dojo/_base/declare',
'react',
"redux",
"react-redux",
"react-components/Reducer"], function (declare,
React,
Redux,
ReactRedux,
Reducer) {
const store = Redux.createStore(Reducer.prototype.rootReducer());
return declare("Store", null, store);
});
- article/Article.jsx
define(['dojo/_base/declare','react'], function (declare, React) {
return declare("Article", [React.Component], {
render() {
return (
{this.props.article.title}
);
}
});
});
- article/Articles.jsx
const Articles = (
React,
Article) => {
return {
render() {
const articles = this.props;
return (
{articles.articles.map(article =>
-
)}
);
}
}
};
define([
'dojo/_base/declare',
'react',
"redux",
"react-redux",
"react-components/article/Article"
], function (declare, React, Redux, ReactRedux, Article) {
return declare(
"Articles",
[React.Component],
Articles(React,
Article));
});
- article/ArticleReducer.jsx
define(['dojo/_base/declare'], function (declare) {
const INITIAL_STATE = {
initArticles: [
{ id: '0', title: 'EXTRA DASHBOARD LAYOUTS ', url: 'https://flex-solution.com/page/alfresco-solution/extra-layouts' },
{ id: '1', title: 'DOCPROCESSOR INTEGRATION ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/docprocessor-integration-add-on' },
{ id: '2', title: 'ALFRESCO MULTIDASHBOARDS ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-multidashboards-add-on' },
{ id: '3', title: 'RESET PASSWORD ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-reset-password-add-on' },
{ id: '4', title: 'ALFRESCO SITES LOGO CHANGER', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-site-logo-changer' },
{ id: '5', title: 'ALFRESCO SOCIAL LOGIN ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-social-login-addon' },
{ id: '6', title: 'SELF REGISTRATION ADD-ON', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-sign-up-addon' },
{ id: '7', title: 'ALFRESCO SHARE THEME BUILDER', url: 'https://flex-solution.com/page/alfresco-solution/alfresco-share-theme-builder' },
{ id: '8', title: 'VIEWCOVID', url: 'https://flex-solution.com/page/portfolio/viewcovid' },
{ id: '9', title: 'INTERCHANGE', url: 'https://flex-solution.com/page/portfolio/interchange-alfresco' },
{ id: '10', title: 'RUBBYHASUI', url: 'https://flex-solution.com/page/portfolio/rubbyhasui-vaadin14' },
{ id: '11', title: 'DOCPROCESSOR', url: 'https://flex-solution.com/page/portfolio/docprocessor' },
],
};
return declare("ArticleReducer", null, {
articleReducer: function articleReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case 'UPDATE_ARTICLE_SET':
return {...state, articles: action.articles};
case 'RESET' :
return state = INITIAL_STATE
default:
return state;
}
}
});
});
- search/Search.jsx
define(['dojo/_base/declare','react'], function (declare, React) {
return declare("Search", [React.Component], {
render() {
return (
{this.props.children}
this.props.onSearch(event.target.value)}
type="text"
className={"flex-input"}/>
);
}
});
});
- search/SearchReducer.jsx
define(['dojo/_base/declare'], function (declare) {
const INITIAL_STATE = {
searchTerm: '',
};
return declare("SearchReducer", null, {
searchReducer: function searchReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case 'SEARCH_SET':
return {...state, searchTerm: action.searchTerm};
default:
return state;
}
}
});
});
The declare
function is defined in the dojo/_base/declare
module. declare
accepts three arguments: `className`, `superClass`, and `properties`.
The className
as an example ('Form', 'Articles') argument represents the name of the class, including the namespace, to be created. Named classes are placed within the global scope. The className
can also represent the inheritance chain via the namespace.
As an example class Form
has an inheritance from React.Component
. An array of classes signifies multiple inheritances. Properties and methods are inherited from left to right. The first class in the array serves as the base prototype, then the subsequent classes are mixins to that class.
If a property or method is specified in more than one inherited class, the property or method from the last inherited class is used.
4. Install Babel
The babel is required for compilation of jsx files. Execute the following commands from the root folder of your extension (pay attention that Node.js must be already installed on your computer):
npm init -y
npm install babel-cli@6 babel-preset-react-app@3
5. Compile .jsx files
Those files above must be compiled with Babel. Run the following command from ${path_to_your_share_module}/src/main/resources/web/js/lib/react-components:
npx babel ./ -d ./ --presets react-app/prod
6. Initialize Alfresco Share page
Next we need to configure a page where our React application will be rendered. To do we have to create 3 files: page descriptor, template instance, page template.
- ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/site-data/pages/react-sample.xml
React Sample
page.reactSample.title
React Sample with Redux(store,connect)
page.reactSample.description
react-sample
none
- ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/site-data/template-instances/react-sample.xml
org/alfresco/react-sample
react-sample
/components/share-reactsample/react-sample
- ${path_to_your_share_module}/src/main/resources/alfresco/web-extension/templates/org/alfresco/react-sample.ftl
<#include "include/alfresco-template.ftl" />
<@templateHeader>
<@link rel="stylesheet" type="text/css" href="${url.context}/res/share-reactsample/react-sample/style.css" />
<@script type="text/javascript" src="${url.context}/res/js/lib/react-components/Runner.js">@script>
@>
<@templateBody>
<@markup id="alf-hd">
<@region scope="global" id="share-header" chromeless="true"/>
@>
<@markup id="bd">
@>
@>
<@templateFooter>
<@markup id="alf-ft">
<@region id="footer" scope="global" />
@>
@>
Pay attention to the div with id reactRender
. It is the main div where our react app will be rendered
The whole project structure is shown on the image below:

That's all what you need to do. Then just compile amp and deploy it to your Alfresco Share. New page will be available by the following URL - http://localhost:8080share/page/react-sample. The final result is presented on the screenshot below:

Source Code
The source code of the module you can clone from our GitHub repo - https://github.com/FlexSolution/AlfrescoShareReactPageSample
Have any questions? Don't hesitate to contact us, we are here and ready to help!