6
\$\begingroup\$

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: [] } }
 ]
}

https://jsfiddle.net/wkmrp4gc/

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Nov 24, 2019 at 1:27
\$\endgroup\$

1 Answer 1

2
+125
\$\begingroup\$

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.

answered Dec 1, 2019 at 6:08
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.