frontend: using reducer to render components

This commit is contained in:
Mose Müller 2023-08-02 12:06:19 +02:00
parent e7350c9ec0
commit 7fe2cc016d

View File

@ -1,13 +1,14 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useReducer } from 'react';
import { Component, ComponentLabel } from './components/component'; import { Component, ComponentLabel } from './components/component';
import { ButtonComponent } from './components/button'; import { ButtonComponent } from './components/button';
import { socket } from './socket'; import { socket } from './socket';
type AttributeType = 'str' | 'bool' | 'float' | 'int' | 'method' | 'Subclass'; type AttributeType = 'str' | 'bool' | 'float' | 'int' | 'method' | 'Subclass';
type ValueType = boolean | string | number | object;
interface Attribute { interface Attribute {
type: AttributeType; type: AttributeType;
value?: any; value?: ValueType;
readonly: boolean; readonly: boolean;
doc?: string | null; doc?: string | null;
parameters?: Record<string, string>; parameters?: Record<string, string>;
@ -15,46 +16,104 @@ interface Attribute {
} }
type MyData = Record<string, Attribute>; type MyData = Record<string, Attribute>;
type State = MyData | null;
type Action =
| { type: 'SET_DATA'; data: MyData }
| { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType };
type NotificationElement = {
data: { parent_path: string; name: string; value: object };
};
/**
* A function to update a specific property in a deeply nested object.
* The property to be updated is specified by a path array.
*
* @param {Array<string>} path - An array where each element is a key in the object,
* forming a path to the property to be updated.
* @param {object} obj - The object to be updated.
* @param {object} value - The new value for the property specified by the path.
* @return {object} - A new object with the specified property updated.
*/
function updateNestedObject(path: Array<string>, obj: object, value: ValueType) {
// Base case: If the path is empty, return the new value.
// This means we've reached the nested property to be updated.
if (path.length === 0) {
return value;
}
// Recursive case: If the path is not empty, split it into the first key and the rest
// of the path.
const [first, ...rest] = path;
// Return a new object that copies all properties of the original object, but updates
// the property specified by 'first'.
// The updated property is an object that copies all properties of the original nested
// object, but updates the 'value' property.
// The new 'value' property is the result of a recursive call to updateNestedObject,
// with the rest of the path, the value of the nested object as the object to be
// updated, and the new value.
return {
...obj,
[first]: {
...obj[first],
value: updateNestedObject(rest, obj[first]?.value || {}, value)
}
};
}
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'SET_DATA':
return action.data;
case 'UPDATE_ATTRIBUTE':
if (!state) {
console.log('No state is set');
return state;
}
{
const path = action.parent_path.split('.').slice(1).concat(action.name);
console.log(path);
return updateNestedObject(path, state, action.value);
}
default:
throw new Error();
}
};
const App = () => { const App = () => {
const [data, setData] = useState<MyData | null>(null); const [state, dispatch] = useReducer(reducer, null);
const [isConnected, setIsConnected] = useState(socket.connected); // const [isConnected, setIsConnected] = useState(socket.connected);
useEffect(() => { useEffect(() => {
// Fetch data from the API when the component mounts // Fetch data from the API when the component mounts
fetch('http://localhost:8001/service-properties') fetch('http://localhost:8001/service-properties')
.then((response) => response.json()) .then((response) => response.json())
.then(setData); .then((data: MyData) => dispatch({ type: 'SET_DATA', data }));
function onConnect() {
setIsConnected(true); function onNotify(value: NotificationElement) {
dispatch({
type: 'UPDATE_ATTRIBUTE',
parent_path: value.data.parent_path,
name: value.data.name,
value: value.data.value
});
} }
function onDisconnect() {
setIsConnected(false);
}
function onNotify(value: Record<string, any>) {
console.log(value);
}
socket.on('connect', onConnect);
socket.on('disconnect', onDisconnect);
socket.on('notify', onNotify); socket.on('notify', onNotify);
return () => { return () => {
socket.off('connect', onConnect);
socket.off('disconnect', onDisconnect);
socket.off('notify', onNotify); socket.off('notify', onNotify);
}; };
}, []); }, []);
// While the data is loading // While the data is loading
if (!data) { if (!state) {
return <p>Loading...</p>; return <p>Loading...</p>;
} }
return ( return (
<div className="App"> <div className="App">
{Object.entries(data).map(([key, value]) => { {Object.entries(state).map(([key, value]) => {
if (value.type === 'bool') { if (value.type === 'bool') {
return ( return (
<div key={key}> <div key={key}>
@ -62,7 +121,7 @@ const App = () => {
name={key} name={key}
docString={value.doc} docString={value.doc}
readOnly={value.readonly} readOnly={value.readonly}
value={value.value} value={Boolean(value.value)}
/> />
</div> </div>
); );