I've been roughing out a proof of concept enabling admin staff to flag submitted applications with issues as needed. We have many forms some of which are nested. So we end up with these big hashes to validate. Primarily we have issues with uploaded images (too blurry, too dark etc) but it could be any field.
So admin may see a field verified_documents -> drivers_licence -> front_image
. If an issue is raised we end up with a hash to store.
{
verified_documents: {
drivers_licence: {
front_image: ['Image is too blurry', 'Image is too dark']
}
}
}
The code works but is very smelly. The function in question is updateIssue
. Which accepts a string representation of the path "verified_documents.drivers_licence.front_image"
and an array of selected options from a dropdown.
The function's intentions:
- Take a copy of current issues, an attempt to be pure and return a new object.
- Split the string into an array.
- Iterate over each value in the path array.
- If path length is the last set its value to the array of issues.
- If the value is an empty array set the value to undefined (only happens if an issue is removed).
- If the key can't be found, create the next level as a child hash.
- Returning the next level of nested object if no other condition is met.
I'm happy I was able to get it all working, but now it's not readable. Readability over performance is my objective, as this code is likely to change.
import { merge, isEmpty } from "lodash";
let Issues = undefined;
const getNestedValue = (nestedObj, pathArr) => {
return pathArr.reduce(
(obj, key) => (obj && obj[key] !== "undefined" ? obj[key] : undefined),
nestedObj
);
};
export class IssuesData {
static updateIssue = (path, value) => {
const valueObj = merge({}, Issues);
const pathArr = path.split(".");
pathArr.reduce((obj, key, i) => {
if (pathArr.length - 1 === i) {
if (isEmpty(value)) {
return (obj[key] = undefined);
}
return (obj[key] = value);
}
if (obj[key] === undefined) {
return (obj[key] = {});
}
return obj[key];
}, valueObj);
Issues = valueObj;
};
static getIssueByKey = path => {
return getNestedValue(Issues, path.split("."));
};
static getIssues() {
return Issues;
}
static setIssues(issues) {
if (!Issues) {
Issues = issues;
}
}
}
UPDATE:
After sleeping on it I managed to clean it up a little. This is more readable but there are still multiple return. If anyone has advice on cleaning it up further it'll be welcomed. I think I'll turn my attention to converting the static methods to instance methods now.
static updateIssue = (path, value) => {
const valueObj = merge({}, Issues);
const pathArray = path.split(".");
pathArray.reduce((obj, key, i) => {
if (isLast(pathArray, i)) {
if (isEmpty(value)) {
return delete obj[key];
}
return (obj[key] = value);
}
return obj[key];
}, valueObj);
Issues = valueObj;
};