From 419fe6a6598afdd3b1a8a998ea5692e1fbf398e0 Mon Sep 17 00:00:00 2001
From: Amir Nakhaei <6696894+amirrr@users.noreply.github.com>
Date: Thu, 21 Dec 2023 04:49:20 +0100
Subject: [PATCH] few components

Signed-off-by: iripiri <ikoester@eonerc.rwth-aachen.de>
---
 src/widget/toolbox-item.js                   |  74 ---------
 src/widget/toolbox-item.jsx                  |  74 +++++++++
 src/widget/widget-plot/plot-legend.js        |  56 -------
 src/widget/widget-plot/plot-legend.jsx       |  62 +++++++
 src/widget/widgets/{action.js => action.jsx} |  43 +++--
 src/widget/widgets/{box.js => box.jsx}       |  32 ++--
 src/widget/widgets/button.js                 | 115 -------------
 src/widget/widgets/button.jsx                | 108 +++++++++++++
 src/widget/widgets/custom-action.js          |  66 --------
 src/widget/widgets/custom-action.jsx         | 118 ++++++++++++++
 src/widget/widgets/{html.js => html.jsx}     |  16 +-
 src/widget/widgets/icstatus.js               |  79 ---------
 src/widget/widgets/icstatus.jsx              |  71 ++++++++
 src/widget/widgets/image.js                  |  89 ----------
 src/widget/widgets/image.jsx                 |  78 +++++++++
 src/widget/widgets/{label.js => label.jsx}   |  29 ++--
 src/widget/widgets/lamp.js                   |  86 ----------
 src/widget/widgets/lamp.jsx                  |  72 +++++++++
 src/widget/widgets/line.js                   |  83 ----------
 src/widget/widgets/line.jsx                  |  70 ++++++++
 src/widget/widgets/plot.js                   | 120 --------------
 src/widget/widgets/plot.jsx                  |  96 +++++++++++
 src/widget/widgets/slider.js                 | 161 -------------------
 src/widget/widgets/slider.jsx                | 148 +++++++++++++++++
 src/widget/widgets/table.js                  | 136 ----------------
 src/widget/widgets/table.jsx                 | 111 +++++++++++++
 26 files changed, 1066 insertions(+), 1127 deletions(-)
 delete mode 100644 src/widget/toolbox-item.js
 create mode 100644 src/widget/toolbox-item.jsx
 delete mode 100644 src/widget/widget-plot/plot-legend.js
 create mode 100644 src/widget/widget-plot/plot-legend.jsx
 rename src/widget/widgets/{action.js => action.jsx} (57%)
 rename src/widget/widgets/{box.js => box.jsx} (59%)
 delete mode 100644 src/widget/widgets/button.js
 create mode 100644 src/widget/widgets/button.jsx
 delete mode 100644 src/widget/widgets/custom-action.js
 create mode 100644 src/widget/widgets/custom-action.jsx
 rename src/widget/widgets/{html.js => html.jsx} (80%)
 delete mode 100644 src/widget/widgets/icstatus.js
 create mode 100644 src/widget/widgets/icstatus.jsx
 delete mode 100644 src/widget/widgets/image.js
 create mode 100644 src/widget/widgets/image.jsx
 rename src/widget/widgets/{label.js => label.jsx} (63%)
 delete mode 100644 src/widget/widgets/lamp.js
 create mode 100644 src/widget/widgets/lamp.jsx
 delete mode 100644 src/widget/widgets/line.js
 create mode 100644 src/widget/widgets/line.jsx
 delete mode 100644 src/widget/widgets/plot.js
 create mode 100644 src/widget/widgets/plot.jsx
 delete mode 100644 src/widget/widgets/slider.js
 create mode 100644 src/widget/widgets/slider.jsx
 delete mode 100644 src/widget/widgets/table.js
 create mode 100644 src/widget/widgets/table.jsx

diff --git a/src/widget/toolbox-item.js b/src/widget/toolbox-item.js
deleted file mode 100644
index 4c268c5..0000000
--- a/src/widget/toolbox-item.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React from 'react';
-import { DragSource } from 'react-dnd';
-import classNames from 'classnames';
-import Icon from '../common/icon';
-
-const toolboxItemSource = {
-  beginDrag(props) {
-    return {
-      name: props.name
-    };
-  }
-};
-
-function collect(connect, monitor) {
-  return {
-    connectDragSource: connect.dragSource(),
-    isDragging: monitor.isDragging()
-  }
-}
-
-class ToolboxItem extends React.Component {
-  static defaultProps = {
-    disabled: false
-  };
-
-  render() {
-    var itemClass = classNames({
-      'toolbox-item': true,
-      'toolbox-item-dragging': this.props.isDragging,
-      'toolbox-item-disabled': this.props.disabled
-    });
-    var dropEffect = 'copy';
-
-    if (this.props.disabled === false) {
-      return this.props.connectDragSource(
-        <div className={itemClass}>
-          <span className="btn " style={{marginTop: '5px'}}>
-            {this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> }
-            {this.props.name}
-          </span>
-        </div>
-      , {dropEffect});
-    }
-    else {
-      return (
-        <div className={itemClass}>
-          <span className="btn btn-info" style={{marginTop: '5px'}}>
-            {this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> }
-            {this.props.name}
-          </span>
-        </div>
-      );
-    }
-  }
-}
-
-export default DragSource('widget', toolboxItemSource, collect)(ToolboxItem);
diff --git a/src/widget/toolbox-item.jsx b/src/widget/toolbox-item.jsx
new file mode 100644
index 0000000..ac5e834
--- /dev/null
+++ b/src/widget/toolbox-item.jsx
@@ -0,0 +1,74 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React from "react";
+import { DragSource } from "react-dnd";
+import classNames from "classnames";
+import Icon from "../common/icon";
+
+// Drag source specification
+const toolboxItemSource = {
+  beginDrag(props) {
+    return {
+      name: props.name,
+    };
+  },
+};
+
+// The collect function gathers props for the component related to dragging
+function collect(connect, monitor) {
+  return {
+    connectDragSource: connect.dragSource(),
+    isDragging: monitor.isDragging(),
+  };
+}
+
+// The functional component for ToolboxItem
+const ToolboxItem = ({
+  connectDragSource,
+  isDragging,
+  disabled,
+  name,
+  icon,
+}) => {
+  const itemClass = classNames({
+    "toolbox-item": true,
+    "toolbox-item-dragging": isDragging,
+    "toolbox-item-disabled": disabled,
+  });
+
+  const content = (
+    <span className="btn " style={{ marginTop: "5px" }}>
+      {icon && <Icon style={{ marginRight: "5px" }} icon={icon} />}
+      {name}
+    </span>
+  );
+
+  return disabled ? (
+    <div className={itemClass}>{content}</div>
+  ) : (
+    connectDragSource(<div className={itemClass}>{content}</div>, {
+      dropEffect: "copy",
+    })
+  );
+};
+
+ToolboxItem.defaultProps = {
+  disabled: false,
+};
+
+export default DragSource("widget", toolboxItemSource, collect)(ToolboxItem);
diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js
deleted file mode 100644
index 096c67b..0000000
--- a/src/widget/widget-plot/plot-legend.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React from 'react';
-import { scaleOrdinal} from 'd3-scale';
-import { schemeCategory10 } from 'd3-scale-chromatic'
-
-function Legend(props){
-
-  const signal = props.sig;
-  const hasScalingFactor = (signal.scalingFactor !== 1);
-
-  let color = typeof props.lineColor === "undefined" ? schemeCategory10[props.index % 10] : props.lineColor;
-
-  return (
-    <li key={signal.id} className="signal-legend" style={{ color: color }}>
-    <span className="signal-legend-name">{signal.name}</span>
-      {props.showUnit ? <span style={{marginLeft: '0.3em'}} className="signal-unit">{signal.unit}</span> : <></> }
-      {hasScalingFactor ? <span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span> : <></>}
-    </li>
-  )
-}
-
-class PlotLegend extends React.Component {
-  render() {
-
-    return <div className="plot-legend">
-      <ul>
-        { this.props.lineColors !== undefined && this.props.lineColors != null ? (
-          this.props.signals.map( (signal, idx) =>
-             <Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={this.props.lineColors[idx]}/>
-        )) : (
-          this.props.signals.map( (signal, idx) =>
-            <Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={"undefined"}/>
-          ))
-        }
-      </ul>
-    </div>;
-  }
-}
-
-export default PlotLegend;
diff --git a/src/widget/widget-plot/plot-legend.jsx b/src/widget/widget-plot/plot-legend.jsx
new file mode 100644
index 0000000..d054e18
--- /dev/null
+++ b/src/widget/widget-plot/plot-legend.jsx
@@ -0,0 +1,62 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React from "react";
+import { schemeCategory10 } from "d3-scale-chromatic";
+
+function Legend(props) {
+  const { sig: signal, lineColor, index, showUnit } = props;
+  const hasScalingFactor = signal.scalingFactor !== 1;
+  const color =
+    typeof lineColor === "undefined" ? schemeCategory10[index % 10] : lineColor;
+
+  return (
+    <li key={signal.id} className="signal-legend" style={{ color: color }}>
+      <span className="signal-legend-name">{signal.name}</span>
+      {showUnit && (
+        <span style={{ marginLeft: "0.3em" }} className="signal-unit">
+          {signal.unit}
+        </span>
+      )}
+      {hasScalingFactor && (
+        <span style={{ marginLeft: "0.3em" }} className="signal-scale">
+          {signal.scalingFactor}
+        </span>
+      )}
+    </li>
+  );
+}
+
+const PlotLegend = ({ signals, lineColors, showUnit }) => {
+  return (
+    <div className="plot-legend">
+      <ul>
+        {signals.map((signal, idx) => (
+          <Legend
+            key={signal.id}
+            sig={signal}
+            showUnit={showUnit}
+            index={idx}
+            lineColor={lineColors ? lineColors[idx] : undefined}
+          />
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default PlotLegend;
diff --git a/src/widget/widgets/action.js b/src/widget/widgets/action.jsx
similarity index 57%
rename from src/widget/widgets/action.js
rename to src/widget/widgets/action.jsx
index 5a69d32..7b0ecb0 100644
--- a/src/widget/widgets/action.js
+++ b/src/widget/widgets/action.jsx
@@ -15,29 +15,28 @@
  * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
  ******************************************************************************/
 
-import React, { Component } from 'react';
-import { Button, ButtonGroup } from 'react-bootstrap';
-import Icon from '../../common/icon';
+import React from "react";
+import { Button, ButtonGroup } from "react-bootstrap";
+import Icon from "../../common/icon";
 
-class WidgetAction extends Component {
-  constructor(props) {
-    super(props);
+const WidgetAction = (props) => {
+  const onClick = (e) => {
+    // Put your onClick logic here
+  };
 
-    this.state = {
-    };
-  }
-
-  onClick(e) {
-
-  }
-
-  render() {
-    return <ButtonGroup>
-            <Button onClick={this.onClick} ><Icon icon="play" /> Start</Button>
-            <Button onClick={this.onClick} ><Icon icon="pause" /> Pause</Button>
-            <Button onClick={this.onClick} ><Icon icon="stop" /> Stop</Button>
-          </ButtonGroup>;
-  }
-}
+  return (
+    <ButtonGroup>
+      <Button onClick={onClick}>
+        <Icon icon="play" /> Start
+      </Button>
+      <Button onClick={onClick}>
+        <Icon icon="pause" /> Pause
+      </Button>
+      <Button onClick={onClick}>
+        <Icon icon="stop" /> Stop
+      </Button>
+    </ButtonGroup>
+  );
+};
 
 export default WidgetAction;
diff --git a/src/widget/widgets/box.js b/src/widget/widgets/box.jsx
similarity index 59%
rename from src/widget/widgets/box.js
rename to src/widget/widgets/box.jsx
index 3572267..3060610 100644
--- a/src/widget/widgets/box.js
+++ b/src/widget/widgets/box.jsx
@@ -15,25 +15,21 @@
  * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
  ******************************************************************************/
 
-import React, { Component } from 'react';
+import React from "react";
 
+const WidgetBox = (props) => {
+  let colorStyle = {
+    borderColor: props.widget.customProperties.border_color,
+    backgroundColor: props.widget.customProperties.background_color,
+    opacity: props.widget.customProperties.background_color_opacity,
+    borderWidth: props.widget.customProperties.border_width + "px",
+  };
 
-class WidgetBox extends Component {
-  render() {
-
-    let colorStyle = {
-      borderColor: this.props.widget.customProperties.border_color,
-      backgroundColor: this.props.widget.customProperties.background_color,
-      opacity: this.props.widget.customProperties.background_color_opacity,
-      borderWidth: this.props.widget.customProperties.border_width + 'px'
-    }
-
-    return (
-      <div className="box-widget full" style={colorStyle}>
-          {  }
-      </div>
-    );
-  }
-}
+  return (
+    <div className="box-widget full" style={colorStyle}>
+      {}
+    </div>
+  );
+};
 
 export default WidgetBox;
diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js
deleted file mode 100644
index ed6e02d..0000000
--- a/src/widget/widgets/button.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React, { Component } from 'react';
-import { Button } from 'react-bootstrap';
-import AppDispatcher from '../../common/app-dispatcher';
-
-class WidgetButton extends Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      pressed: props.widget.customProperties.pressed
-    }
-  }
-
-  componentDidMount() {
-    let widget = this.props.widget
-    widget.customProperties.simStartedSendValue = false
-    widget.customProperties.pressed = false
-
-    AppDispatcher.dispatch({
-      type: 'widgets/start-edit',
-      token: this.props.token,
-      data: widget
-    });
-  }
-
-  componentDidUpdate() {
-    // a simulaton was started, make an update
-    if (this.props.widget.customProperties.simStartedSendValue) {
-      // update widget, 'unpress' button at each simulation start
-      let widget = this.props.widget
-      widget.customProperties.simStartedSendValue = false
-      widget.customProperties.pressed = false
-      AppDispatcher.dispatch({
-        type: 'widgets/start-edit',
-        token: this.props.token,
-        data: widget
-      });
-
-      // send value without changing widget
-      this.props.onInputChanged(widget.customProperties.off_value, '', false, false);
-    }
-  }
-
-  static getDerivedStateFromProps(props, state){
-    return {
-      pressed: props.widget.customProperties.pressed
-    }
-  }
-
-  onPress(e) {
-    if (e.button === 0 && !this.props.widget.customProperties.toggle) {
-      this.valueChanged(this.props.widget.customProperties.on_value, true);
-    }
-  }
-
-  onRelease(e) {
-    if (e.button === 0) {
-      let nextState = false;
-      if (this.props.widget.customProperties.toggle) {
-        nextState = !this.state.pressed;
-      }
-      this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value, nextState);
-    }
-  }
-
-  valueChanged(newValue, pressed) {
-    if (this.props.onInputChanged) {
-      this.props.onInputChanged(newValue, 'pressed', pressed, true);
-    }
-  }
-
-  render() {
-    let opacity = this.props.widget.customProperties.background_color_opacity
-    const buttonStyle = {
-      backgroundColor: this.props.widget.customProperties.background_color,
-      borderColor: this.props.widget.customProperties.border_color,
-      color: this.props.widget.customProperties.font_color,
-      opacity: this.state.pressed ? (opacity + 1)/4 : opacity,
-    };
-
-    return (
-      <div className="button-widget full">
-          <Button
-            className="full"
-            style={buttonStyle}
-            active={ this.state.pressed }
-            disabled={ this.props.editing }
-            onMouseDown={ (e) => this.onPress(e) }
-            onMouseUp={ (e) => this.onRelease(e) }>
-            {this.props.widget.name}
-          </Button>
-      </div>
-    );
-  }
-}
-
-export default WidgetButton;
diff --git a/src/widget/widgets/button.jsx b/src/widget/widgets/button.jsx
new file mode 100644
index 0000000..7dced13
--- /dev/null
+++ b/src/widget/widgets/button.jsx
@@ -0,0 +1,108 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState, useEffect } from 'react';
+import { Button } from 'react-bootstrap';
+import AppDispatcher from '../../common/app-dispatcher';
+
+const WidgetButton = (props) => {
+  const [pressed, setPressed] = useState(props.widget.customProperties.pressed);
+
+  useEffect(() => {
+    let widget = props.widget;
+    widget.customProperties.simStartedSendValue = false;
+    widget.customProperties.pressed = false;
+
+    AppDispatcher.dispatch({
+      type: 'widgets/start-edit',
+      token: props.token,
+      data: widget
+    });
+
+    // Effect cleanup
+    return () => {
+      // Clean up if needed
+    };
+  }, [props.token, props.widget]);
+
+  useEffect(() => {
+    if (props.widget.customProperties.simStartedSendValue) {
+      let widget = props.widget;
+      widget.customProperties.simStartedSendValue = false;
+      widget.customProperties.pressed = false;
+      AppDispatcher.dispatch({
+        type: 'widgets/start-edit',
+        token: props.token,
+        data: widget
+      });
+
+      props.onInputChanged(widget.customProperties.off_value, '', false, false);
+    }
+  }, [props, setPressed]);
+
+  useEffect(() => {
+    setPressed(props.widget.customProperties.pressed);
+  }, [props.widget.customProperties.pressed]);
+
+  const onPress = (e) => {
+    if (e.button === 0 && !props.widget.customProperties.toggle) {
+      valueChanged(props.widget.customProperties.on_value, true);
+    }
+  };
+
+  const onRelease = (e) => {
+    if (e.button === 0) {
+      let nextState = false;
+      if (props.widget.customProperties.toggle) {
+        nextState = !pressed;
+      }
+      valueChanged(nextState ? props.widget.customProperties.on_value : props.widget.customProperties.off_value, nextState);
+    }
+  };
+
+  const valueChanged = (newValue, newPressed) => {
+    if (props.onInputChanged) {
+      props.onInputChanged(newValue, 'pressed', newPressed, true);
+    }
+    setPressed(newPressed);
+  };
+
+  let opacity = props.widget.customProperties.background_color_opacity;
+  const buttonStyle = {
+    backgroundColor: props.widget.customProperties.background_color,
+    borderColor: props.widget.customProperties.border_color,
+    color: props.widget.customProperties.font_color,
+    opacity: pressed ? (opacity + 1) / 4 : opacity,
+  };
+
+  return (
+    <div className="button-widget full">
+      <Button
+        className="full"
+        style={buttonStyle}
+        active={pressed}
+        disabled={props.editing}
+        onMouseDown={(e) => onPress(e)}
+        onMouseUp={(e) => onRelease(e)}
+      >
+        {props.widget.name}
+      </Button>
+    </div>
+  );
+};
+
+export default WidgetButton;
diff --git a/src/widget/widgets/custom-action.js b/src/widget/widgets/custom-action.js
deleted file mode 100644
index cdd8078..0000000
--- a/src/widget/widgets/custom-action.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React, { Component } from 'react';
-import { Button } from 'react-bootstrap';
-import Icon from '../../common/icon';
-import ICStore from '../../ic/ic-store';
-import AppDispatcher from '../../common/app-dispatcher';
-
-class WidgetCustomAction extends Component {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      ic: null
-    };
-  }
-
-  static getStores() {
-    return [ ICStore ];
-  }
-
-  static getDerivedStateFromProps(props, state){
-    if(props.widget.signalIDs.length === 0){
-      return null;
-    }
-
-    return{
-      ic: ICStore.getState().find(s => s.id === props.icIDs[0]),
-      sessionToken: localStorage.getItem("token")
-    };
-  }
-
-  onClick() {
-    AppDispatcher.dispatch({
-      type: 'ics/start-action',
-      ic: this.state.ic,
-      data: this.props.widget.customProperties.actions,
-      token: this.state.sessionToken
-    });
-  }
-
-  render() {
-    return <div className="widget-custom-action full">
-      <Button className="full" disabled={this.state.ic === null} onClick={(e) => this.onClick()}>
-        <Icon icon={this.props.widget.customProperties.icon} /> <span dangerouslySetInnerHTML={{ __html: this.props.widget.name }} />
-      </Button>
-    </div>;
-  }
-}
-
-export default WidgetCustomAction;
diff --git a/src/widget/widgets/custom-action.jsx b/src/widget/widgets/custom-action.jsx
new file mode 100644
index 0000000..1073249
--- /dev/null
+++ b/src/widget/widgets/custom-action.jsx
@@ -0,0 +1,118 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+import React, { useState, useEffect } from "react";
+import { Button } from "react-bootstrap";
+import Icon from "../../common/icon";
+import ICStore from "../../ic/ic-store";
+import AppDispatcher from "../../common/app-dispatcher";
+
+const WidgetCustomAction = (props) => {
+  const [ic, setIC] = useState(null);
+  const [sessionToken, setSessionToken] = useState(
+    localStorage.getItem("token")
+  );
+
+  useEffect(() => {
+    const handleStoresChanged = () => {
+      if (props.widget.signalIDs.length === 0) {
+        setIC(null);
+        return;
+      }
+
+      const newIC = ICStore.getState().find((s) => s.id === props.icIDs[0]);
+      setIC(newIC);
+    };
+
+    // Subscribe to store changes
+    const unsubscribe = ICStore.subscribe(handleStoresChanged);
+    handleStoresChanged(); // Also call it to set initial state
+
+    return () => {
+      unsubscribe(); // Clean up the subscription when the component is unmounted
+    };
+  }, [props.widget.signalIDs, props.icIDs]);
+
+  const onClick = () => {
+    AppDispatcher.dispatch({
+      type: "ics/start-action",
+      ic: ic,
+      data: props.widget.customProperties.actions,
+      token: sessionToken,
+    });
+  };
+
+  return (
+    <div className="widget-custom-action full">
+      <Button className="full" disabled={ic === null} onClick={onClick}>
+        <Icon icon={props.widget.customProperties.icon} />
+        <span dangerouslySetInnerHTML={{ __html: props.widget.name }} />
+      </Button>
+    </div>
+  );
+};
+
+export default WidgetCustomAction;
+
+// import React, { Component } from 'react';
+// import { Button } from 'react-bootstrap';
+// import Icon from '../../common/icon';
+// import ICStore from '../../ic/ic-store';
+// import AppDispatcher from '../../common/app-dispatcher';
+
+// class WidgetCustomAction extends Component {
+//   constructor(props) {
+//     super(props);
+
+//     this.state = {
+//       ic: null
+//     };
+//   }
+
+//   static getStores() {
+//     return [ ICStore ];
+//   }
+
+//   static getDerivedStateFromProps(props, state){
+//     if(props.widget.signalIDs.length === 0){
+//       return null;
+//     }
+
+//     return{
+//       ic: ICStore.getState().find(s => s.id === props.icIDs[0]),
+//       sessionToken: localStorage.getItem("token")
+//     };
+//   }
+
+//   onClick() {
+//     AppDispatcher.dispatch({
+//       type: 'ics/start-action',
+//       ic: this.state.ic,
+//       data: this.props.widget.customProperties.actions,
+//       token: this.state.sessionToken
+//     });
+//   }
+
+//   render() {
+//     return <div className="widget-custom-action full">
+//       <Button className="full" disabled={this.state.ic === null} onClick={(e) => this.onClick()}>
+//         <Icon icon={this.props.widget.customProperties.icon} /> <span dangerouslySetInnerHTML={{ __html: this.props.widget.name }} />
+//       </Button>
+//     </div>;
+//   }
+// }
+
+// export default WidgetCustomAction;
diff --git a/src/widget/widgets/html.js b/src/widget/widgets/html.jsx
similarity index 80%
rename from src/widget/widgets/html.js
rename to src/widget/widgets/html.jsx
index e5c18a0..1f3774f 100644
--- a/src/widget/widgets/html.js
+++ b/src/widget/widgets/html.jsx
@@ -15,12 +15,16 @@
  * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
  ******************************************************************************/
 
-import React from 'react';
+import React from "react";
 
-class WidgetHTML extends React.Component {
-  render() {
-    return <div dangerouslySetInnerHTML={{__html: this.props.widget.customProperties.content }} />
-  }
-}
+const WidgetHTML = (props) => {
+  return (
+    <div
+      dangerouslySetInnerHTML={{
+        __html: props.widget.customProperties.content,
+      }}
+    />
+  );
+};
 
 export default WidgetHTML;
diff --git a/src/widget/widgets/icstatus.js b/src/widget/widgets/icstatus.js
deleted file mode 100644
index 2bb9d9b..0000000
--- a/src/widget/widgets/icstatus.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React from 'react';
-import { Badge } from 'react-bootstrap';
-import { stateLabelStyle } from "../../ic/ics";
-import AppDispatcher from '../../common/app-dispatcher';
-
-
-class WidgetICstatus extends React.Component {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      sessionToken: localStorage.getItem("token")
-    };
-  }
-
-  componentDidMount() {
-    // Start timer for periodic refresh
-    this.timer = window.setInterval(() => this.refresh(), 3000);
-  }
-
-  componentWillUnmount() {
-    window.clearInterval(this.timer);
-  }
-
-  refresh() {
-    if (this.props.ics) {
-      this.props.ics.forEach(ic => {
-        let icID = parseInt(ic.id, 10);
-        AppDispatcher.dispatch({
-          type: 'ics/start-load',
-          data: icID,
-          token: this.state.sessionToken,
-        });
-      })
-    }
-  }
-
-  render() {
-    let badges = []
-    let checkedICs = []
-    if (this.props.widget) {
-      checkedICs = this.props.widget.customProperties.checkedIDs
-    }
-    if (this.props.ics && checkedICs) {
-      this.props.ics.forEach(ic => {
-        if (!checkedICs.includes(ic.id)) {
-          return
-        }
-        let badgeStyle = stateLabelStyle(ic.state, ic)
-        badges.push(<Badge
-          key={ic.id}
-          bg={badgeStyle[0]}
-          className={badgeStyle[1]}>
-          {ic.name + ": " + ic.state}</Badge>)
-      })
-    }
-
-    return (<div>{badges}</div>);
-  }
-}
-
-export default WidgetICstatus;
diff --git a/src/widget/widgets/icstatus.jsx b/src/widget/widgets/icstatus.jsx
new file mode 100644
index 0000000..19b7dba
--- /dev/null
+++ b/src/widget/widgets/icstatus.jsx
@@ -0,0 +1,71 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState, useEffect } from "react";
+import { Badge } from "react-bootstrap";
+import { stateLabelStyle } from "../../ic/ics";
+import AppDispatcher from "../../common/app-dispatcher";
+
+const WidgetICstatus = (props) => {
+  const [sessionToken, setSessionToken] = useState(
+    localStorage.getItem("token")
+  );
+
+  useEffect(() => {
+    // Function to refresh data
+    const refresh = () => {
+      if (props.ics) {
+        props.ics.forEach((ic) => {
+          let icID = parseInt(ic.id, 10);
+          AppDispatcher.dispatch({
+            type: "ics/start-load",
+            data: icID,
+            token: sessionToken,
+          });
+        });
+      }
+    };
+
+    // Start timer for periodic refresh
+    const timer = window.setInterval(() => refresh(), 3000);
+
+    // Cleanup function equivalent to componentWillUnmount
+    return () => {
+      window.clearInterval(timer);
+    };
+  }, [props.ics, sessionToken]);
+
+  let badges = [];
+  let checkedICs = props.widget ? props.widget.customProperties.checkedIDs : [];
+
+  if (props.ics && checkedICs) {
+    badges = props.ics
+      .filter((ic) => checkedICs.includes(ic.id))
+      .map((ic) => {
+        let badgeStyle = stateLabelStyle(ic.state, ic);
+        return (
+          <Badge key={ic.id} bg={badgeStyle[0]} className={badgeStyle[1]}>
+            {ic.name + ": " + ic.state}
+          </Badge>
+        );
+      });
+  }
+
+  return <div>{badges}</div>;
+};
+
+export default WidgetICstatus;
diff --git a/src/widget/widgets/image.js b/src/widget/widgets/image.js
deleted file mode 100644
index f289324..0000000
--- a/src/widget/widgets/image.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React from 'react';
-
-import AppDispatcher from '../../common/app-dispatcher';
-
-class WidgetImage extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      file: undefined,
-    }
-  }
-
-  componentDidMount() {
-    // Query the image referenced by the widget
-    let widgetFile = this.props.widget.customProperties.file;
-    if (widgetFile !== -1 && this.state.file === undefined) {
-      AppDispatcher.dispatch({
-        type: 'files/start-download',
-        data: widgetFile,
-        token: this.props.token
-      });
-    }
-  }
-
-  componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) {
-
-    if(this.props.widget.customProperties.file === -1){
-      this.props.widget.customProperties.update = false;
-      if(this.state.file !== undefined) this.setState({ file: undefined })
-    }
-
-    let file = this.props.files.find(file => file.id === parseInt(this.props.widget.customProperties.file, 10));
-
-    if (file !== undefined) {
-      if (this.props.widget.customProperties.update) {
-        this.props.widget.customProperties.update = false;
-        AppDispatcher.dispatch({
-          type: 'files/start-download',
-          data: file.id,
-          token: this.props.token
-        });
-        this.setState({ file: file })
-      }
-    }
-
-  }
-
-  imageError(e){
-    console.error("Image ", this.state.file.name, "cannot be displayed.");
-  }
-
-  render() {
-    let objectURL=''
-    if(this.state.file !== undefined && this.state.file.objectURL !== undefined) {
-      objectURL = this.state.file.objectURL
-    }
-
-    return (
-      <div className="full">
-        {objectURL !== '' ? (
-          <img onError={(e) => this.imageError(e)} className="full" alt={this.state.file.name} src={objectURL} onDragStart={e => e.preventDefault()} />
-        ) : (
-          <img className="full" alt="No file selected." />
-        )}
-      </div>
-    );
-  }
-}
-
-export default WidgetImage;
diff --git a/src/widget/widgets/image.jsx b/src/widget/widgets/image.jsx
new file mode 100644
index 0000000..8b658d8
--- /dev/null
+++ b/src/widget/widgets/image.jsx
@@ -0,0 +1,78 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState, useEffect } from "react";
+import AppDispatcher from "../../common/app-dispatcher";
+
+const WidgetImage = (props) => {
+  const [file, setFile] = useState(undefined);
+
+  useEffect(() => {
+    let widgetFile = props.widget.customProperties.file;
+    if (widgetFile !== -1 && file === undefined) {
+      AppDispatcher.dispatch({
+        type: "files/start-download",
+        data: widgetFile,
+        token: props.token,
+      });
+    }
+  }, [file, props.token, props.widget.customProperties.file]);
+
+  useEffect(() => {
+    if (props.widget.customProperties.file === -1) {
+      props.widget.customProperties.update = false;
+      if (file !== undefined) setFile(undefined);
+    } else {
+      let foundFile = props.files.find(
+        (f) => f.id === parseInt(props.widget.customProperties.file, 10)
+      );
+      if (foundFile && props.widget.customProperties.update) {
+        props.widget.customProperties.update = false;
+        AppDispatcher.dispatch({
+          type: "files/start-download",
+          data: foundFile.id,
+          token: props.token,
+        });
+        setFile(foundFile);
+      }
+    }
+  }, [props.widget.customProperties, props.files, props.token, file]);
+
+  const imageError = (e) => {
+    console.error("Image error:", e);
+  };
+
+  let objectURL = file && file.objectURL ? file.objectURL : "";
+
+  return (
+    <div className="full">
+      {objectURL ? (
+        <img
+          onError={imageError}
+          className="full"
+          alt={file.name}
+          src={objectURL}
+          onDragStart={(e) => e.preventDefault()}
+        />
+      ) : (
+        <img className="full" alt="No file selected." />
+      )}
+    </div>
+  );
+};
+
+export default WidgetImage;
diff --git a/src/widget/widgets/label.js b/src/widget/widgets/label.jsx
similarity index 63%
rename from src/widget/widgets/label.js
rename to src/widget/widgets/label.jsx
index 2a29e1f..141d1c7 100644
--- a/src/widget/widgets/label.js
+++ b/src/widget/widgets/label.jsx
@@ -15,23 +15,20 @@
  * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
  ******************************************************************************/
 
-import React, { Component } from 'react';
+import React from "react";
 
+const WidgetLabel = (props) => {
+  const style = {
+    fontSize: props.widget.customProperties.textSize + "px",
+    color: props.widget.customProperties.fontColor,
+    opacity: props.widget.customProperties.fontColor_opacity,
+  };
 
-class WidgetLabel extends Component {
-  render() {
-      const style = {
-      fontSize: this.props.widget.customProperties.textSize + 'px',
-      color: this.props.widget.customProperties.fontColor,
-      opacity: this.props.widget.customProperties.fontColor_opacity,
-    };
-
-    return (
-      <div className="label-widget">
-        <h4 style={style}>{this.props.widget.name}</h4>
-      </div>
-    );
-  }
-}
+  return (
+    <div className="label-widget">
+      <h4 style={style}>{props.widget.name}</h4>
+    </div>
+  );
+};
 
 export default WidgetLabel;
diff --git a/src/widget/widgets/lamp.js b/src/widget/widgets/lamp.js
deleted file mode 100644
index 43603e4..0000000
--- a/src/widget/widgets/lamp.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React, { Component } from 'react';
-
-
-class WidgetLamp extends Component {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      value: '',
-    };
-  }
-
-  static getDerivedStateFromProps(props, state){
-    if(props.widget.signalIDs.length === 0){
-      return{ value: ''};
-    }
-
-    // get the signal with the selected signal ID
-    let signalID = props.widget.signalIDs[0];
-    let signal = props.signals.filter(s => s.id === signalID)
-
-    if(signal.length>0) {
-      // determine ID of infrastructure component related to signal[0] (there is only one signal for a lamp widget)
-      let icID = props.icIDs[signal[0].id];
-
-      // check if data available
-      if (props.data == null
-        || props.data[icID] == null
-        || props.data[icID].output == null
-        || props.data[icID].output.values == null) {
-        return {value: ''};
-      }
-
-      // check if value has changed
-      const data = props.data[icID].output.values[signal[0].index];
-      if (data != null && Number(state.value) !== signal[0].scalingFactor * data[data.length - 1].y) {
-        return {value: signal[0].scalingFactor * data[data.length - 1].y};
-      }
-    }
-
-    return null;
-  }
-
-  render() {
-
-    let color;
-    let opacity;
-
-    if (Number(this.state.value) > Number(this.props.widget.customProperties.threshold)){
-      color = this.props.widget.customProperties.on_color;
-      opacity = this.props.widget.customProperties.on_color_opacity;
-    }
-    else{
-      color = this.props.widget.customProperties.off_color;
-      opacity = this.props.widget.customProperties.off_color_opacity;
-    }
-
-    let style = {
-      backgroundColor: color,
-      opacity: opacity
-    }
-
-    return (
-      <div className="lamp-widget" style={style} />
-    );
-  }
-}
-
-export default WidgetLamp;
diff --git a/src/widget/widgets/lamp.jsx b/src/widget/widgets/lamp.jsx
new file mode 100644
index 0000000..c44cdd0
--- /dev/null
+++ b/src/widget/widgets/lamp.jsx
@@ -0,0 +1,72 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState, useEffect } from 'react';
+
+const WidgetLamp = (props) => {
+  const [value, setValue] = useState('');
+
+  useEffect(() => {
+    if(props.widget.signalIDs.length === 0){
+      setValue('');
+      return;
+    }
+
+    let signalID = props.widget.signalIDs[0];
+    let signal = props.signals.find(s => s.id === signalID);
+    
+    if(signal) {
+      let icID = props.icIDs[signal.id];
+      
+      if (!(props.data &&
+            props.data[icID] &&
+            props.data[icID].output &&
+            props.data[icID].output.values)) {
+        setValue('');
+        return;
+      }
+      
+      const data = props.data[icID].output.values[signal.index];
+      if(data) {
+        const newValue = String(signal.scalingFactor * data[data.length - 1].y);
+        if (value !== newValue) {
+          setValue(newValue);
+        }
+      }
+    }
+  }, [props.widget.signalIDs, props.signals, props.icIDs, props.data, value]);
+
+  let color, opacity;
+  if (Number(value) > Number(props.widget.customProperties.threshold)) {
+    color = props.widget.customProperties.on_color;
+    opacity = props.widget.customProperties.on_color_opacity;
+  } else {
+    color = props.widget.customProperties.off_color;
+    opacity = props.widget.customProperties.off_color_opacity;
+  }
+
+  let style = {
+    backgroundColor: color,
+    opacity: opacity
+  };
+
+  return (
+    <div className="lamp-widget" style={style} />
+  );
+};
+
+export default WidgetLamp;
\ No newline at end of file
diff --git a/src/widget/widgets/line.js b/src/widget/widgets/line.js
deleted file mode 100644
index 2250907..0000000
--- a/src/widget/widgets/line.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React, { Component } from 'react';
-
-class WidgetLine extends Component {
-  // WidgetLine is newly created when widget props are changed and saved
-  constructor(props)  {
-    super(props);
-    this.illustrateDuringEdit = this.illustrateDuringEdit.bind(this);
-
-    this.state = {
-      width: 0,
-      height: 0,
-      editing: false
-    }
-  }
-
-  // needed to update the looks of the line in edit mode
-   illustrateDuringEdit(newwidth, newheight) {
-     this.setState({width: newwidth, height: newheight, editing: true});
-   }
-
-  render() {
-    let rotation = this.props.widget.customProperties.rotation;
-    let rad = rotation * (Math.PI / 180);
-
-    // get the dimensions either from props (saved widget)
-    // or from the state (widget in edit mode)
-    let width = this.props.widget.width;
-    let height = this.props.widget.height;
-
-    if (this.state.editing) {
-      width = this.state.width;
-      height = this.state.height;
-    }
-
-    let length = width;
-    rotation = Math.abs(parseInt(rotation,10));
-    if(rotation % 90 === 0 && (rotation/90) % 2 === 1){
-      length = height;
-    }
-
-    // calculate line coordinates (in percent)
-    const x1 = width * 0.5 - 0.5 * Math.cos(rad) * length;
-    const x1p = '' + Math.round(100 * x1 / width) + '%';
-
-    const x2 = width * 0.5 + 0.5 * Math.cos(rad) * length;
-    const x2p = '' + Math.round(100 * x2/width) + '%';
-
-    const y1 = height * 0.5 + 0.5 * Math.sin(rad) * length;
-    const y1p = '' + Math.round(100 * y1/height) + '%';
-
-    const y2 = height * 0.5 - 0.5 * Math.sin(rad) * length;
-    const y2p = '' + Math.round(100 * y2/height) + '%';
-
-
-    const lineStyle = {
-        stroke: '' +  this.props.widget.customProperties.border_color,
-        strokeWidth: '' + this.props.widget.customProperties.border_width + 'px'
-    };
-
-      return <svg height="100%" width="100%">
-                <line x1={x1p} x2={x2p} y1={y1p} y2={y2p} style={lineStyle}/>
-              </svg>;
-  }
-}
-
-export default WidgetLine;
diff --git a/src/widget/widgets/line.jsx b/src/widget/widgets/line.jsx
new file mode 100644
index 0000000..d1f6648
--- /dev/null
+++ b/src/widget/widgets/line.jsx
@@ -0,0 +1,70 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState } from "react";
+
+const WidgetLine = (props) => {
+  const [dimensions, setDimensions] = useState({
+    width: props.widget.width || 0,
+    height: props.widget.height || 0,
+    editing: false,
+  });
+
+  const illustrateDuringEdit = (newWidth, newHeight) => {
+    setDimensions({ width: newWidth, height: newHeight, editing: true });
+  };
+
+  // Assuming illustrateDuringEdit may be called from outside to update the dimensions.
+  // If not, you can remove this line.
+  props.illustrateDuringEditRef &&
+    props.illustrateDuringEditRef(illustrateDuringEdit);
+
+  let { rotation } = props.widget.customProperties;
+  let rad = rotation * (Math.PI / 180);
+  let length = dimensions.editing ? dimensions.width : props.widget.width;
+
+  rotation = Math.abs(parseInt(rotation, 10));
+  if (rotation % 90 === 0 && (rotation / 90) % 2 === 1) {
+    length = dimensions.editing ? dimensions.height : props.widget.height;
+  }
+
+  // calculate line coordinates (in percent)
+  const x1 = length * 0.5 - 0.5 * Math.cos(rad) * length;
+  const x1p = `${Math.round((100 * x1) / length)}%`;
+
+  const x2 = length * 0.5 + 0.5 * Math.cos(rad) * length;
+  const x2p = `${Math.round((100 * x2) / length)}%`;
+
+  const y1 = length * 0.5 + 0.5 * Math.sin(rad) * length;
+  const y1p = `${Math.round((100 * y1) / length)}%`;
+
+  const y2 = length * 0.5 - 0.5 * Math.sin(rad) * length;
+  const y2p = `${Math.round((100 * y2) / length)}%`;
+
+  const lineStyle = {
+    stroke: props.widget.customProperties.border_color,
+    strokeWidth: `${props.widget.customProperties.border_width}px`,
+  };
+
+  return (
+    <svg height="100%" width="100%">
+      <line x1={x1p} x2={x2p} y1={y1p} y2={y2p} style={lineStyle} />
+    </svg>
+  );
+};
+
+export default WidgetLine;
diff --git a/src/widget/widgets/plot.js b/src/widget/widgets/plot.js
deleted file mode 100644
index 27041b0..0000000
--- a/src/widget/widgets/plot.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React from 'react';
-
-import Plot from '../widget-plot/plot';
-import PlotLegend from '../widget-plot/plot-legend';
-
-class WidgetPlot extends React.Component {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      data: [],
-      signals: []
-    };
-  }
-
-
-  static getDerivedStateFromProps(props, state){
-
-    let intersection = []
-    let data = [];
-    let signalID, sig;
-    for (signalID of props.widget.signalIDs) {
-      for (sig of props.signals) {
-        if (signalID === sig.id) {
-          intersection.push(sig);
-
-          // sig is a selected signal, get data
-          // determine ID of infrastructure component related to signal (via config)
-          let icID = props.icIDs[sig.id]
-
-          // distinguish between input and output signals
-          if (sig.direction === "out") {
-            if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) {
-              if (props.data[icID].output.values[sig.index] !== undefined) {
-                let values = props.data[icID].output.values[sig.index];
-                if(sig.scalingFactor !== 1) {
-                  let scaledValues = JSON.parse(JSON.stringify(values));
-                  for (let i=0; i< scaledValues.length; i++){
-                    scaledValues[i].y = scaledValues[i].y * sig.scalingFactor;
-                  }
-                  data.push(scaledValues);
-                } else {
-                  data.push(values);
-                }
-              }
-            }
-          } else if (sig.direction === "in") {
-            if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) {
-              if (props.data[icID].input.values[sig.index] !== undefined) {
-                let values = props.data[icID].output.values[sig.index];
-                if(sig.scalingFactor !== 1) {
-                  let scaledValues = JSON.parse(JSON.stringify(values));
-                  for (let i=0; i< scaledValues.length; i++){
-                    scaledValues[i].y = scaledValues[i].y * sig.scalingFactor;
-                  }
-                  data.push(scaledValues);
-                } else {
-                  data.push(values);
-                }
-              }
-            }
-          }
-        } // sig is selected signal
-      } // loop over props.signals
-    } // loop over selected signals
-
-    return {signals: intersection, data: data}
-
-  }
-
-  //do we need this function?
-  scaleData(data, scaleFactor){
-    // data is an array of value pairs x,y
-  }
-
-  render() {
-    return <div className="plot-widget" ref="wrapper">
-      <div className="widget-plot">
-        <Plot
-          data={this.state.data}
-          mode={this.props.widget.customProperties.mode || "auto time-scrolling"}
-          height={this.props.widget.height - 55}
-          width={this.props.widget.width - 20}
-          time={this.props.widget.customProperties.time}
-          samples={this.props.widget.customProperties.nbrSamples || 100}
-          yMin={this.props.widget.customProperties.yMin}
-          yMax={this.props.widget.customProperties.yMax}
-          yUseMinMax={this.props.widget.customProperties.yUseMinMax}
-          paused={this.props.paused}
-          yLabel={this.props.widget.customProperties.ylabel}
-          lineColors={this.props.widget.customProperties.lineColors}
-          signalIDs={this.props.widget.signalIDs}
-        />
-      </div>
-      <PlotLegend
-        signals={this.state.signals}
-        lineColors={this.props.widget.customProperties.lineColors}
-        showUnit={this.props.widget.customProperties.showUnit} />
-    </div>;
-  }
-}
-
-export default WidgetPlot;
diff --git a/src/widget/widgets/plot.jsx b/src/widget/widgets/plot.jsx
new file mode 100644
index 0000000..74d5a16
--- /dev/null
+++ b/src/widget/widgets/plot.jsx
@@ -0,0 +1,96 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState, useEffect } from "react";
+import Plot from "../widget-plot/plot";
+import PlotLegend from "../widget-plot/plot-legend";
+
+const WidgetPlot = (props) => {
+  const [data, setData] = useState([]);
+  const [signals, setSignals] = useState([]);
+
+  useEffect(() => {
+    const intersection = [];
+    const plotData = [];
+    let signalID, sig;
+    for (signalID of props.widget.signalIDs) {
+      for (sig of props.signals) {
+        if (signalID === sig.id) {
+          intersection.push(sig);
+
+          // Signal is a selected signal, get data
+          let icID = props.icIDs[sig.id];
+          let values = null;
+
+          if (
+            sig.direction === "out" &&
+            props.data[icID]?.output?.values?.[sig.index] !== undefined
+          ) {
+            values = props.data[icID].output.values[sig.index];
+          } else if (
+            sig.direction === "in" &&
+            props.data[icID]?.input?.values?.[sig.index] !== undefined
+          ) {
+            values = props.data[icID].input.values[sig.index];
+          }
+
+          if (values) {
+            if (sig.scalingFactor !== 1) {
+              values = values.map((v) => ({
+                ...v,
+                y: v.y * sig.scalingFactor,
+              }));
+            }
+            plotData.push(values);
+          }
+        }
+      }
+    }
+
+    setData(plotData);
+    setSignals(intersection);
+  }, [props.widget.signalIDs, props.signals, props.icIDs, props.data]);
+
+  return (
+    <div className="plot-widget">
+      <div className="widget-plot">
+        <Plot
+          data={data}
+          mode={props.widget.customProperties.mode || "auto time-scrolling"}
+          height={props.widget.height - 55}
+          width={props.widget.width - 20}
+          time={props.widget.customProperties.time}
+          samples={props.widget.customProperties.nbrSamples || 100}
+          yMin={props.widget.customProperties.yMin}
+          yMax={props.widget.customProperties.yMax}
+          yUseMinMax={props.widget.customProperties.yUseMinMax}
+          paused={props.paused}
+          yLabel={props.widget.customProperties.ylabel}
+          lineColors={props.widget.customProperties.lineColors}
+          signalIDs={props.widget.signalIDs}
+        />
+      </div>
+      <PlotLegend
+        signals={signals}
+        lineColors={props.widget.customProperties.lineColors}
+        showUnit={props.widget.customProperties.showUnit}
+      />
+    </div>
+  );
+};
+
+export default WidgetPlot;
diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js
deleted file mode 100644
index ea7be28..0000000
--- a/src/widget/widgets/slider.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React, { Component } from 'react';
-import { format } from 'd3';
-import classNames from 'classnames';
-import Slider from 'rc-slider';
-import 'rc-slider/assets/index.css';
-import AppDispatcher from '../../common/app-dispatcher';
-
-
-class WidgetSlider extends Component {
-
-  static get OrientationTypes() {
-    return ({
-      HORIZONTAL: {value: 0, name: 'Horizontal'},
-      VERTICAL: {value: 1, name: 'Vertical'}
-    })
-  }
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-        unit: '',
-        value: '',
-    };
-  }
-
-  componentDidMount() {
-    let widget = this.props.widget
-    widget.customProperties.simStartedSendValue = false
-    AppDispatcher.dispatch({
-      type: 'widgets/start-edit',
-      token: this.props.token,
-      data: widget
-    });
-  }
-
-  componentDidUpdate() {
-    // a simulaton was started, make an update
-    if (this.props.widget.customProperties.simStartedSendValue) {
-      let widget = this.props.widget
-      widget.customProperties.simStartedSendValue = false
-      AppDispatcher.dispatch({
-        type: 'widgets/start-edit',
-        token: this.props.token,
-        data: widget
-      });
-
-      // send value without changing widget
-      this.props.onInputChanged(widget.customProperties.value , '', '', false);
-    }
-  }
-
-  static getDerivedStateFromProps(props, state){
-
-    let value = ''
-    let unit = ''
-
-    if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){
-      // set value to customProperties.value if this property exists and the value is different from current state
-      value = Number(props.widget.customProperties.value);
-    } else if (state.value === '') {
-      value = 0.0;
-    }
-
-    // Update unit (assuming there is exactly one signal for this widget)
-    if (props.widget.signalIDs.length > 0) {
-      let signalID = props.widget.signalIDs[0];
-      let signal = props.signals.find(sig => sig.id === signalID);
-      if (signal !== undefined) {
-        unit = signal.unit;
-      }
-    }
-
-    if (unit !== '' && value !== ''){
-      // unit and value have changed
-      return {unit: unit, value: value};
-    } else if (unit !== ''){
-      // only unit has changed
-      return {unit: unit}
-    } else if (value !== ''){
-      // only value has changed
-      return {value: value}
-    } else {
-      // nothing has changed
-      return null
-    }
-  }
-
-  valueIsChanging(newValue) {
-    this.props.widget.customProperties.value = newValue;
-    if (this.props.widget.customProperties.continous_update)
-      this.valueChanged(newValue, false);
-
-    this.setState({ value: newValue });
-  }
-
-  valueChanged(newValue, isFinalChange) {
-    if (this.props.onInputChanged) {
-      this.props.onInputChanged(newValue, 'value', newValue, isFinalChange);
-    }
-  }
-
-  render() {
-
-    let isVertical = this.props.widget.customProperties.orientation === WidgetSlider.OrientationTypes.VERTICAL.value;
-    let fields = {
-      name: this.props.widget.name,
-      control: <Slider
-        min={ this.props.widget.customProperties.rangeMin }
-        max={ this.props.widget.customProperties.rangeMax }
-        step={ this.props.widget.customProperties.step }
-        value={ this.state.value }
-        disabled={ this.props.editing }
-        vertical={ isVertical }
-        onChange={ (v) => this.valueIsChanging(v) }
-        onAfterChange={ (v) => this.valueChanged(v, true)
-        }/>,
-      value: <span className="signal-value">{ format('.2f')(Number.parseFloat(this.state.value)) }</span>,
-      unit: <span className="signal-unit">{ this.state.unit }</span>
-    }
-
-    let widgetClasses = classNames({
-                    'slider-widget': true,
-                    'full': true,
-                    'vertical': isVertical,
-                    'horizontal': !isVertical
-                  });
-
-    return (
-      <>
-      <div >
-        { fields.name }
-        { fields.value }
-        {this.props.widget.customProperties.showUnit && fields.unit}
-      </div>
-      <div className={widgetClasses}>
-        { fields.control }
-      </div>
-      </>
-    );
-  }
-}
-
-export default WidgetSlider;
diff --git a/src/widget/widgets/slider.jsx b/src/widget/widgets/slider.jsx
new file mode 100644
index 0000000..89d33fd
--- /dev/null
+++ b/src/widget/widgets/slider.jsx
@@ -0,0 +1,148 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+
+import React, { useState, useEffect } from "react";
+import { format } from "d3";
+import classNames from "classnames";
+import Slider from "rc-slider";
+import "rc-slider/assets/index.css";
+import AppDispatcher from "../../common/app-dispatcher";
+
+const WidgetSlider = (props) => {
+  const [value, setValue] = useState("");
+  const [unit, setUnit] = useState("");
+
+  useEffect(() => {
+    let widget = { ...props.widget };
+    widget.customProperties.simStartedSendValue = false;
+    AppDispatcher.dispatch({
+      type: "widgets/start-edit",
+      token: props.token,
+      data: widget,
+    });
+  }, [props.token, props.widget]);
+
+  useEffect(() => {
+    // A simulation was started, make an update
+    if (props.widget.customProperties.simStartedSendValue) {
+      let widget = { ...props.widget };
+      widget.customProperties.simStartedSendValue = false;
+      AppDispatcher.dispatch({
+        type: "widgets/start-edit",
+        token: props.token,
+        data: widget,
+      });
+
+      // Send value without changing widget
+      props.onInputChanged(widget.customProperties.value, "", "", false);
+    }
+  }, [props.token, props.widget, props.onInputChanged]);
+
+  useEffect(() => {
+    let newValue = "";
+    let newUnit = "";
+
+    if (
+      props.widget.customProperties.hasOwnProperty("value") &&
+      props.widget.customProperties.value !== value
+    ) {
+      newValue = Number(props.widget.customProperties.value);
+    } else if (value === "") {
+      newValue = 0.0;
+    }
+
+    if (props.widget.signalIDs.length > 0) {
+      let signalID = props.widget.signalIDs[0];
+      let signal = props.signals.find((sig) => sig.id === signalID);
+      if (signal !== undefined) {
+        newUnit = signal.unit;
+      }
+    }
+
+    if (newUnit) {
+      setUnit(newUnit);
+    }
+    if (newValue !== "") {
+      setValue(newValue);
+    }
+  }, [props.signals, props.widget, value]);
+
+  const valueIsChanging = (newValue) => {
+    props.widget.customProperties.value = newValue;
+    if (props.widget.customProperties.continous_update) {
+      valueChanged(newValue, false);
+    }
+    setValue(newValue);
+  };
+
+  const valueChanged = (newValue, isFinalChange) => {
+    if (props.onInputChanged) {
+      props.onInputChanged(newValue, "value", newValue, isFinalChange);
+    }
+  };
+
+  let isVertical =
+    props.widget.customProperties.orientation ===
+    WidgetSlider.OrientationTypes.VERTICAL.value;
+
+  const fields = {
+    name: props.widget.name,
+    control: (
+      <Slider
+        min={props.widget.customProperties.rangeMin}
+        max={props.widget.customProperties.rangeMax}
+        step={props.widget.customProperties.step}
+        value={value}
+        disabled={props.editing}
+        vertical={isVertical}
+        onChange={valueIsChanging}
+        onAfterChange={(v) => valueChanged(v, true)}
+      />
+    ),
+    value: (
+      <span className="signal-value">
+        {format(".2f")(Number.parseFloat(value))}
+      </span>
+    ),
+    unit: <span className="signal-unit">{unit}</span>,
+  };
+
+  const widgetClasses = classNames({
+    "slider-widget": true,
+    full: true,
+    vertical: isVertical,
+    horizontal: !isVertical,
+  });
+
+  return (
+    <>
+      <div>
+        {fields.name}
+        {fields.value}
+        {props.widget.customProperties.showUnit && fields.unit}
+      </div>
+      <div className={widgetClasses}>{fields.control}</div>
+    </>
+  );
+};
+
+WidgetSlider.OrientationTypes = {
+  HORIZONTAL: { value: 0, name: "Horizontal" },
+  VERTICAL: { value: 1, name: "Vertical" },
+};
+
+export default WidgetSlider;
diff --git a/src/widget/widgets/table.js b/src/widget/widgets/table.js
deleted file mode 100644
index 7e0f8d0..0000000
--- a/src/widget/widgets/table.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * This file is part of VILLASweb.
- *
- * VILLASweb is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * VILLASweb is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
-
-import React, { Component } from 'react';
-import { format } from 'd3';
-
-import { Table, DataColumn } from '../../common/table';
-
-class WidgetTable extends Component {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      rows: [],
-    };
-  }
-
-  static getDerivedStateFromProps(props, state){
-
-    let rows = [];
-    let signalID, sig;
-    for (signalID of props.widget.signalIDs) {
-      for (sig of props.signals) {
-        if (signalID === sig.id) {
-          // sig is a selected signal, get data
-          // determine ID of infrastructure component related to signal (via config)
-          let icID = props.icIDs[sig.id]
-
-          // distinguish between input and output signals
-          if (sig.direction === "out") {
-            if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) {
-              if (props.data[icID].output.values[sig.index] !== undefined) {
-                let data = props.data[icID].output.values[sig.index];
-                rows.push({
-                  name: sig.name,
-                  unit: sig.unit,
-                  value: data[data.length - 1].y * sig.scalingFactor,
-                  scalingFactor: sig.scalingFactor
-                });
-
-              } else {
-                // no data available
-                rows.push({
-                  name: sig.name,
-                  unit: sig.unit,
-                  value: NaN,
-                  scalingFactor: sig.scalingFactor
-                })
-              }
-            }
-          } else if (sig.direction === "in") {
-            if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) {
-              if (props.data[icID].input.values[sig.index] !== undefined) {
-                let data = props.data[icID].input.values[sig.index];
-                rows.push({
-                  name: sig.name,
-                  unit: sig.unit,
-                  value: data[data.length - 1].y * sig.scalingFactor,
-                  scalingFactor: sig.scalingFactor
-                });
-              } else {
-                // no data available
-                rows.push({
-                  name: sig.name,
-                  unit: sig.unit,
-                  value: NaN,
-                  scalingFactor: sig.scalingFactor
-                })
-              }
-            }
-          }
-        } // sig is selected signal
-      } // loop over props.signals
-    } // loop over selected signals
-
-    return {rows: rows}
-
-  }
-
-  render() {
-
-
-    let showScalingFactor;
-    if (this.props.widget.customProperties.showScalingFactor !== undefined){ // this line ensures backwards compatibility with older versions of VILLASweb
-      showScalingFactor = this.props.widget.customProperties.showScalingFactor;
-    } else {
-      showScalingFactor = true;
-    }
-
-    let rows = this.state.rows;
-
-    if(rows.length === 0){
-      rows.push({
-        name: "no entries"
-      })
-    }
-
-    let columns = [
-      <DataColumn key={1} title="Signal" dataKey="name" width={120} />,
-      <DataColumn key={2} title="Value" dataKey="value" modifier={format('.4f')} />,
-    ];
-
-    let nextKey = 3;
-    if (showScalingFactor) {
-      columns.push(<DataColumn key={nextKey} title="Scale" dataKey="scalingFactor" modifier={format('.2f')}/>);
-      nextKey++;
-    }
-    if (this.props.widget.customProperties.showUnit) {
-      columns.push(<DataColumn key={nextKey} title="Unit" dataKey="unit"/>);
-    }
-
-    return (
-      <div className="table-widget" style={{width: this.props.widget.width, height: this.props.widget.height, overflowY: 'auto'}}>
-        <Table data={rows}>
-          { columns }
-        </Table>
-      </div>
-    );
-  }
-}
-
-export default WidgetTable;
diff --git a/src/widget/widgets/table.jsx b/src/widget/widgets/table.jsx
new file mode 100644
index 0000000..e87fce7
--- /dev/null
+++ b/src/widget/widgets/table.jsx
@@ -0,0 +1,111 @@
+/**
+ * This file is part of VILLASweb.
+ *
+ * VILLASweb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VILLASweb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
+import React, { useState, useEffect } from "react";
+import { format } from "d3";
+import { Table, DataColumn } from "../../common/table";
+
+const WidgetTable = (props) => {
+  const [rows, setRows] = useState([]);
+
+  useEffect(() => {
+    let newRows = [];
+    let signalID, sig;
+    for (signalID of props.widget.signalIDs) {
+      for (sig of props.signals) {
+        if (signalID === sig.id) {
+          let icID = props.icIDs[sig.id];
+
+          let direction = sig.direction === "out" ? "output" : "input";
+          if (
+            props.data[icID] &&
+            props.data[icID][direction] &&
+            props.data[icID][direction].values
+          ) {
+            if (props.data[icID][direction].values[sig.index] !== undefined) {
+              let data = props.data[icID][direction].values[sig.index];
+              newRows.push({
+                name: sig.name,
+                unit: sig.unit,
+                value: data[data.length - 1].y * sig.scalingFactor,
+                scalingFactor: sig.scalingFactor,
+              });
+            } else {
+              newRows.push({
+                name: sig.name,
+                unit: sig.unit,
+                value: NaN,
+                scalingFactor: sig.scalingFactor,
+              });
+            }
+          }
+        }
+      }
+    }
+
+    if (newRows.length === 0) {
+      newRows.push({ name: "no entries" });
+    }
+
+    setRows(newRows);
+  }, [props.widget.signalIDs, props.signals, props.icIDs, props.data]);
+
+  let showScalingFactor =
+    props.widget.customProperties.showScalingFactor !== undefined
+      ? props.widget.customProperties.showScalingFactor
+      : true;
+
+  let columns = [
+    <DataColumn key={1} title="Signal" dataKey="name" width={120} />,
+    <DataColumn
+      key={2}
+      title="Value"
+      dataKey="value"
+      modifier={format(".4f")}
+    />,
+  ];
+
+  let nextKey = 3;
+  if (showScalingFactor) {
+    columns.push(
+      <DataColumn
+        key={nextKey}
+        title="Scale"
+        dataKey="scalingFactor"
+        modifier={format(".2f")}
+      />
+    );
+    nextKey++;
+  }
+  if (props.widget.customProperties.showUnit) {
+    columns.push(<DataColumn key={nextKey} title="Unit" dataKey="unit" />);
+  }
+
+  return (
+    <div
+      className="table-widget"
+      style={{
+        width: props.widget.width,
+        height: props.widget.height,
+        overflowY: "auto",
+      }}
+    >
+      <Table data={rows}>{columns}</Table>
+    </div>
+  );
+};
+
+export default WidgetTable;