
Product
Reachability for Ruby Now in Beta
Reachability analysis for Ruby is now in beta, helping teams identify which vulnerabilities are truly exploitable in their applications.
@iobroker/adapter-react-v5
Advanced tools
You can find demo on https://github.com/ioBroker/adapter-react-demo
If you want to create the configuration page with ReactJS:
npx create-react-app src . It will take a while.cd srcname from src to ADAPTERNAME-admin (Of course replace ADAPTERNAME with yours){ "@iobroker/adapter-react-v5": "^7.4.10" }
Versions can be higher.
So your src/package.json should look like:{
"name": "ADAPTERNAME-admin",
"version": "0.1.0",
"private": true,
"dependencies": {
"@iobroker/adapter-react-v5": "^7.4.10",
"@iobroker/build-tools": "^1.0.0",
"babel-eslint": "^10.1.0",
"react-scripts": "^5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"homepage": ".",
"browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"]
}
src: npm installtasks.js into src: cp node_modules/@iobroker/adapter-react-v5/tasks.js tasks.jspackage.json scripts section: {
"scripts": {
"0-clean": "node tasks --0-clean",
"1-npm": "node tasks --1-npm",
"2-build": "node tasks --2-build",
"3-copy": "node tasks --3-copy",
"4-patch": "node tasks --4-patch",
"build": "node tasks"
}
}
npm run start for developing or build with npm run build and
copy files in build directory to www or to admin. In the admin you must rename index.html to index_m.html.npm tasks: npm run buildsocket.io to public/index.html.
After<link
rel="manifest"
href="%PUBLIC_URL%/manifest.json"
/>
insert
<script>
const script = document.createElement('script');
window.registerSocketOnLoad = function (cb) {
window.socketLoadedHandler = cb;
};
const parts = (window.location.search || '').replace(/^\?/, '').split('&');
const query = {};
parts.forEach(item => {
const [name, val] = item.split('=');
query[decodeURIComponent(name)] = val !== undefined ? decodeURIComponent(val) : true;
});
script.onload = function () {
typeof window.socketLoadedHandler === 'function' && window.socketLoadedHandler();
};
script.src =
window.location.port === '3000'
? window.location.protocol +
'//' +
(query.host || window.location.hostname) +
':' +
(query.port || 8081) +
'/lib/js/socket.io.js'
: '%PUBLIC_URL%/../../lib/js/socket.io.js';
document.head.appendChild(script);
</script>
class App extends GenericApp {
constructor(props) {
const extendedProps = { ...props };
extendedProps.encryptedFields = ['pass']; // this parameter will be encrypted and decrypted automatically
extendedProps.translations = {
en: require('./i18n/en'),
de: require('./i18n/de'),
ru: require('./i18n/ru'),
pt: require('./i18n/pt'),
nl: require('./i18n/nl'),
fr: require('./i18n/fr'),
it: require('./i18n/it'),
es: require('./i18n/es'),
pl: require('./i18n/pl'),
uk: require('./i18n/uk'),
'zh-cn': require('./i18n/zh-cn'),
};
// get actual admin port
extendedProps.socket = { port: parseInt(window.location.port, 10) };
// Only if close, save buttons are not required at the bottom (e.g. if admin tab)
// extendedProps.bottomButtons = false;
// only for debug purposes
if (extendedProps.socket.port === 3000) {
extendedProps.socket.port = 8081;
}
// allow to manage GenericApp the sentry initialization or do not set the sentryDSN if no sentry available
extendedProps.sentryDSN = 'https://yyy@sentry.iobroker.net/xx';
super(extendedProps);
}
// ...
}
index.js with the following code to support themes:import React from 'react';
import { createRoot } from 'react-dom/client';
import * as serviceWorker from './serviceWorker';
import './index.css';
import App from './App';
import { version } from '../package.json';
console.log(`iobroker.scenes@${version}`);
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
class App extends GenericApp {
// ...
onPrepareLoad(settings) {
settings.pass = this.decode(settings.pass);
}
onPrepareSave(settings) {
settings.pass = this.encode(settings.pass);
}
}
onPrepareSave(settings) {
super.onPrepareSave(settings);
if (DATA_INVALID) {
return false; // configuration will not be saved
} else {
return true;
}
}
This is a non-React class to provide the communication for socket connection with the server.
Some dialogs are predefined and could be used out of the box.
Usage:
import React from 'react';
import { I18n, Confirm as ConfirmDialog } from '@iobroker/adapter-react-v5';
class ExportImportDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
confirmDialog: false,
};
}
renderConfirmDialog() {
if (!this.state.confirmDialog) {
return null;
}
return (
<ConfirmDialog
title={I18n.t('Scene will be overwritten.')}
text={I18n.t('All data will be lost. Confirm?')}
ok={I18n.t('Yes')}
cancel={I18n.t('Cancel')}
suppressQuestionMinutes={5}
dialogName="myConfirmDialogThatCouldBeSuppressed"
suppressText={I18n.t('Suppress question for next %s minutes', 5)}
onClose={isYes => {
this.setState({ confirmDialog: false });
}}
/>
);
}
render() {
return (
<div>
<Button onClick={() => this.setState({ confirmDialog: true })}>Click</Button>
{this.renderConfirmDialog()}
</div>
);
}
}
export default ExportImportDialog;
renderMessage() {
if (this.state.showMessage) {
return <Message
text={this.state.showMessage}
onClose={() => this.setState({showMessage: false})}
/>;
} else {
return null;
}
}

import { SelectID as DialogSelectID } from '@iobroker/adapter-react-v5';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
showSelectId: false,
};
}
renderSelectIdDialog() {
if (this.state.showSelectId) {
return (
<DialogSelectID
key="tableSelect"
imagePrefix="../.."
dialogName={this.props.adapterName}
themeType={this.props.themeType}
socket={this.props.socket}
statesOnly={true}
selected={this.state.selectIdValue}
onClose={() => this.setState({ showSelectId: false })}
onOk={(selected, name) => {
this.setState({ showSelectId: false, selectIdValue: selected });
}}
/>
);
} else {
return null;
}
}
render() {
return renderSelectIdDialog();
}
}
Include "react-text-mask": "^5.4.3", in package.json.
function renderCron() {
if (!showCron) {
return null;
} else {
return (
<DialogCron
key="dialogCron1"
cron={this.state.cronValue || '* * * * *'}
onClose={() => this.setState({ showCron: false })}
onOk={cronValue => {
this.setState({ cronValue });
}}
/>
);
}
}
getObjectNameFromObj(obj, settings, options, isDesc)
Get object name from a single object.
Usage: Utils.getObjectNameFromObj(this.objects[id], null, {language: I18n.getLanguage()})
getObjectIcon(id, obj)
Get icon from the object.
Usage:
const icon = Utils.getObjectIcon(id, this.objects[id]);
return <img src={icon} />;
isUseBright(color, defaultValue)
Usage: `

render() {
if (!this.state.loaded) {
return <MuiThemeProvider theme={this.state.theme}>
<Loader theme={this.state.themeType}/>
</MuiThemeProvider>;
}
// render loaded data
}

render() {
return <form className={this.props.classes.tab}>
<Logo
instance={this.props.instance}
common={this.props.common}
native={this.props.native}
onError={text => this.setState({errorText: text})}
onLoad={this.props.onLoad}
/>
...
</form>;
}
It is better to use Dialog/SelectID, but if you want:

<ObjectBrowser
foldersFirst={this.props.foldersFirst}
imagePrefix={this.props.imagePrefix || this.props.prefix} // prefix is for back compatibility
defaultFilters={this.filters}
dialogName={this.dialogName}
showExpertButton={this.props.showExpertButton !== undefined ? this.props.showExpertButton : true}
style={{ width: '100%', height: '100%' }}
columns={this.props.columns || ['name', 'type', 'role', 'room', 'func', 'val']}
types={this.props.types || ['state']}
t={I18n.t}
lang={this.props.lang || I18n.getLanguage()}
socket={this.props.socket}
selected={this.state.selected}
multiSelect={this.props.multiSelect}
notEditable={this.props.notEditable === undefined ? true : this.props.notEditable}
name={this.state.name}
theme={this.props.theme}
themeName={this.props.themeName}
themeType={this.props.themeType}
customFilter={this.props.customFilter}
onFilterChanged={filterConfig => {
this.filters = filterConfig;
window.localStorage.setItem(this.dialogName, JSON.stringify(filterConfig));
}}
onSelect={(selected, name, isDouble) => {
if (JSON.stringify(selected) !== JSON.stringify(this.state.selected)) {
this.setState({ selected, name }, () => isDouble && this.handleOk());
} else if (isDouble) {
this.handleOk();
}
}}
/>

// STYLES
const styles = {
tableDiv: {
width: '100%',
overflow: 'hidden',
height: 'calc(100% - 48px)',
},
};
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: [
{
id: 'UniqueID1', // required
fieldIdInData: 'Name1',
myType: 'number',
},
{
id: 'UniqueID2', // required
fieldIdInData: 'Name12',
myType: 'string',
},
],
};
this.columns = [
{
title: 'Name of field', // required, else it will be "field"
field: 'fieldIdInData', // required
editable: false, // or true [default - true]
cellStyle: {
// CSS style - // optional
maxWidth: '12rem',
overflow: 'hidden',
wordBreak: 'break-word',
},
lookup: {
// optional => edit will be automatically "SELECT"
value1: 'text1',
value2: 'text2',
},
},
{
title: 'Type', // required, else it will be "field"
field: 'myType', // required
editable: true, // or true [default - true]
lookup: {
// optional => edit will be automatically "SELECT"
number: 'Number',
string: 'String',
boolean: 'Boolean',
},
type: 'number/string/color/oid/icon/boolean', // oid=ObjectID,icon=base64-icon
editComponent: props => (
<div>
Prefix{ <br />
<textarea
rows={4}
style={{ width: '100%', resize: 'vertical' }}
value={props.value}
onChange={e => props.onChange(e.target.value)}
/>
Suffix
</div>
),
},
];
}
// renderTable
render() {
return (
<div className={this.props.classes.tableDiv}>
<TreeTable
columns={this.columns}
data={this.state.data}
onUpdate={(newData, oldData) => {
const data = JSON.parse(JSON.stringify(this.state.data));
// Added new line
if (newData === true) {
// find unique ID
let i = 1;
let id = 'line_' + i;
// eslint-disable-next-line
while (this.state.data.find(item => item.id === id)) {
i++;
id = 'line_' + i;
}
data.push({
id,
name: I18n.t('New resource') + '_' + i,
color: '',
icon: '',
unit: '',
price: 0,
});
} else {
// existing line was modifed
const pos = this.state.data.indexOf(oldData);
if (pos !== -1) {
Object.keys(newData).forEach(attr => (data[pos][attr] = newData[attr]));
}
}
this.setState({ data });
}}
onDelete={oldData => {
console.log('Delete: ' + JSON.stringify(oldData));
const pos = this.state.data.indexOf(oldData);
if (pos !== -1) {
const data = JSON.parse(JSON.stringify(this.state.data));
data.splice(pos, 1);
this.setState({ data });
}
}}
/>
</div>
);
}
}
Toast is not a part of adapter-react but it is an example how to use toast in application:
import { Component } from 'react';
import { Snackbar } from '@mui/material';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
// ....
toast: '',
};
}
// ...
renderToast() {
if (!this.state.toast) {
return null;
}
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={true}
autoHideDuration={6000}
onClose={() => this.setState({ toast: '' })}
ContentProps={{ 'aria-describedby': 'message-id' }}
message={<span id="message-id">{this.state.toast}</span>}
action={[
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={this.props.classes.close}
onClick={() => this.setState({ toast: '' })}
>
<IconClose />
</IconButton>,
]}
/>
);
}
render() {
return <div>{this.renderToast()}</div>;
}
}
In dialogs, the OK button is first (on the left) and the cancel button is last (on the right)
This project uses icons from Flaticon.
ioBroker GmbH has a valid license for all the used icons. The icons may not be reused in other projects without the proper flaticon license or flaticon subscription.
You can find the migration instructions:
updateSmartNameEx function to Utilssocket.iob instead of socket.iocronstruewithStyles in favor of sx and style properties (see Migration from v5 to v6@mui/material (5.15.20) is usedmodulefederation.admin.config.js for module federation(@GermanBluefox) Updated packages
(@GermanBluefox) Updated packages
SelectID dialogcopyToClipboard event parameter optionalSelectID scrolling@iobroker/json-config@iobroker/json-config moved to real dependencies@iobroker/json-config module to have a single point of truthfilterFunc as string@iobroker/typessubscribeOnInstance for Connection classsubscribeStateAsync method to wait for answergetObjectsById to the socket communicationcraco-module-federation.js was added. For node 16TextWithIcon with defined color and iconechartschartReady event was omittedmateriallog command@iobroker/socket-client instead of Connection.tsxrenderTextWithA function to support <b> and <i> tagsfullWidth property to DialoginvertColorgetCompactSystemRepositoriesObjectBrowserI18n.disableWarning methodlog method to connectionThe MIT License (MIT)
Copyright © 2019-2025 @GermanBluefox dogafox@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FAQs
React components to develop ioBroker interfaces with react.
We found that @iobroker/adapter-react-v5 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 6 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Reachability analysis for Ruby is now in beta, helping teams identify which vulnerabilities are truly exploitable in their applications.

Research
/Security News
Malicious npm packages use Adspect cloaking and fake CAPTCHAs to fingerprint visitors and redirect victims to crypto-themed scam sites.

Security News
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.