My goal here is to make data driven nested forms in React. I've created a function that points to a form configuration JSON, which can contain both components and references to other configuration files, with their own components. I'm splitting it to separate files to keep things DRY and to get an easier overview (hierarchy can be about 5 levels deep, one JSON file would be rather hard to overview).
I've introduced $ref
to be able to point to another JSON configuration and $extends
for inheritance (configurations can differ dependings on context but have common properties). args
will be spread out as props to the component.
config-builder.js
function createConfiguration(configuration) {
return _.reduce(configuration.components, (result, data) => {
// if it's a reference to another configuration - fetch it - otherwise use as is
const component = data.$ref ? getComponentByReference(data) : data
return addResult(result, component.key, component.components ? { ...component, components: createConfiguration(component) } : component)
}, {})
}
function getComponentByReference(data) {
const component = _.merge({}, configMap[data.$ref], data)
const result = component.$extends ? _.merge({}, configMap[component.$extends], component) : component
// clear internal values like $ref, $extends
return _.omitBy(result, (value, key) => _.startsWith(key, '$'))
}
// use an array to wrap results to handle multiple keys of the same type on same level
function addResult(result, key, data) {
if(!result[key]) {
result[key] = [data]
} else {
result[key].push(data)
}
return result
}
foo.json
{
"key": "foo",
"type": "DefaultContainer",
"components": [
{ "key": "name", "type": "Text" },
{ "$ref": "Bar" }
]
}
bar.json
{
"key": "bar",
"type": "DefaultContainer",
"components": [
{ "key": "id", "type": "DropDown", "args": { options: [] } }
]
}
1 Answer 1
Good things
This code uses const
for variables that don't get re-assigned.
The functions are concise, though some of the lines are a bit long due to ternary operators.
Suggestions
Bear in mind that functional programming is typically slower than imperative because each iteration leads to a function being added to the call stack. This would be noticeable with large data sets. Having the function addResult()
seems like an excess step since it is only called in one spot. The lines to ensure the object at the given key is an array and push an element to that array could simply exist in the callback to the _.reduce()
call.
Also, instead of either creating a new array with the data item or pushing it into an existing array, it could create an empty array when appropriate and then always push the data. This may be slightly less performant but requires fewer lines of code than storing the data in a temporary variable.
function createConfiguration(configuration) {
return _.reduce(configuration.components, (result, data) => {
const component = data.$ref ? getComponentByReference(data) : data;
if(!result[component.key]) {
result[component.key] = [];
}
result[component.key].push(component.components ? { ...component, components: createConfiguration(component) } : component)
return result;
}, {});
}
Unless you fully understand the ways Automatic semicolon insertion can be broken by certain statements, add semi-colons to terminate the lines.
Explore related questions
See similar questions with these tags.