frontend: updates reducer to process serialized values

This commit is contained in:
Mose Müller 2023-11-16 09:14:48 +01:00
parent f693fa9ba2
commit 4a43bda5e2

View File

@ -8,96 +8,32 @@ import {
import './App.css';
import { Notifications } from './components/NotificationsComponent';
import { ConnectionToast } from './components/ConnectionToast';
import { SerializedValue, setNestedValueByPath, State } from './utils/stateUtils';
type ValueType = boolean | string | number | object;
type State = DataServiceJSON | null;
type Action =
| { type: 'SET_DATA'; data: DataServiceJSON }
| { type: 'UPDATE_ATTRIBUTE'; parentPath: string; name: string; value: ValueType };
| { type: 'SET_DATA'; data: State }
| {
type: 'UPDATE_ATTRIBUTE';
parentPath: string;
name: string;
value: SerializedValue;
};
type UpdateMessage = {
data: { parent_path: string; name: string; value: object };
data: { parent_path: string; name: string; value: SerializedValue };
};
type ExceptionMessage = {
data: { exception: string; type: string };
};
/**
* A function to update a specific property in a deeply nested object.
* The property to be updated is specified by a path array.
*
* Each path element can be a regular object key or an array index of the
* form "attribute[index]", where "attribute" is the key of the array in
* the object and "index" is the index of the element in the array.
*
* For array indices, the element at the specified index in the array is
* updated.
*
* If the property to be updated is an object or an array, it is updated
* recursively.
*/
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;
// Check if 'first' is an array index.
const indexMatch = first.match(/^(\w+)\[(\d+)\]$/);
// If 'first' is an array index of the form "attribute[index]", then update the
// element at the specified index in the array. Otherwise, update the property
// specified by 'first' in the object.
if (indexMatch) {
const attribute = indexMatch[1];
const index = parseInt(indexMatch[2]);
if (Array.isArray(obj[attribute]?.value)) {
return {
...obj,
[attribute]: {
...obj[attribute],
value: obj[attribute].value.map((item, i) =>
i === index
? {
...item,
value: updateNestedObject(rest, item.value || {}, value)
}
: item
)
}
};
} else {
throw new Error(
`Expected ${attribute}.value to be an array, but received ${typeof obj[
attribute
]?.value}`
);
}
} else {
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': {
const path = action.parentPath.split('.').slice(1).concat(action.name);
const pathList = action.parentPath.split('.').slice(1).concat(action.name);
const joinedPath = pathList.join('.');
return updateNestedObject(path, state, action.value);
return setNestedValueByPath(state, joinedPath, action.value);
}
default:
throw new Error();
@ -137,7 +73,7 @@ const App = () => {
// Fetch data from the API when the client connects
fetch(`http://${hostname}:${port}/service-properties`)
.then((response) => response.json())
.then((data: DataServiceJSON) => dispatch({ type: 'SET_DATA', data }));
.then((data: State) => dispatch({ type: 'SET_DATA', data }));
setConnectionStatus('connected');
});
socket.on('disconnect', () => {