diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 80f1a74..87bff6d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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, 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', () => {