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/

\$\endgroup\$
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.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.