0

I'm looking for advice on the best way to dynamically create elements from JSON in React using mapping with components.

I figure we can have each input type as a separate component class and construct the element passing in props and bind the changes.

E.g. the JSON:

    {
        "type": "text",
        "name": "FirstName",                    
        "class": "text",
        "placeholder": "Enter first name"
    },
    {
        "type": "text",
        "name": "Surname",                  
        "class": "text",
        "placeholder": "Enter surname"
    },

React:

class InputText extends React.Component {
  constructor(props) {
    super(props);
    this.changeValue = this.changeValue.bind(this);
  }

  render() {
    return (
      <div className={className}>
        <label htmlFor={this.props.name}>{this.props.title}</label>
        <input
          onChange={this.changeValue}
          name={this.props.name}
          type={this.props.type}
          value={this.props.value}
        />
      </div>
    );
  }
}

Any advice on the best way to iterate through (and validate) each item in the JSON? Am I on the right track here? Thanks!

3 Answers 3

2

You need a component that maps over the data and returns the collection of InputTexts. Here I've called it Main.

class Main extends React.Component {
  render() {

    // Map over the data and return the completed component
    // On each iteration pass in the object data as `params`.
    // You can deconstruct this in the `InputText` component
    return data.map((input, i) => {
      return <InputText key={i} params={input} />
    });
  }
}

Now you can pick up those params in the component by deconstructing this.props.params and using those variables to fill out the component.

class InputText extends React.Component {

  constructor(props) {
    super(props);
    this.changeValue = this.changeValue.bind(this);
  }

  changeValue() {
    console.log('Placeholder to prevent bug');
  }

  render() {

   // Use destructuring to grab the individual properties from params
   const { type, name, classname, placeholder } = this.props.params;

    // Use those properties in the returned component elements
    return (
      <div className={classname}>
        <label htmlFor={name}>Test</label>
        <input
          onChange={this.changeValue}
          name={name}
          type={type}
          placeholder={placeholder}
        />
      </div>
    );
  }
}

DEMO

Sign up to request clarification or add additional context in comments.

1 Comment

A simple and elegant solution, this makes a lot of sense to me. Thanks so much for your answer.
1

InputText.jsx

Upon interaction each InputText will raise onChange saying which input field was edited and what is its current value.

import * as React from 'react';

export class InputText extends React.Component {
  onChange = (e) => {
    const {onChange, name} = this.props;
    const {value} = e.target;
    if (onChange) {
      onChange(name, value);
    }
  }

  render() {
    const {name, title, type, placeholder, className, value} = this.props;
    return (
      <div className={className}>
        <label htmlFor={name}>{title}</label>
        <input
          placeholder={placeholder}
          name={name}
          type={type}
          value={value}
          onChange={this.onChange}
        />
      </div>
    );
  }
}

Form.jsx

Here we maintain the state of all inputs. The weird reduce is done to initialise the shape of the state with the input names being the object properties.

// initial state
{
  "FirstName": "",
  "Surname": ""
}

Upon edit this.setState({[name]: value}) the associated property gets updated.

import * as React from 'react';
import { InputText } from './InputText';

const inputs = [{
  "type": "text",
  "title": "some title",
  "name": "FirstName",
  "class": "text",
  "placeholder": "Enter first name"
}, {
  "type": "text",
  "title": "some other title",
  "name": "Surname",
  "class": "text",
  "placeholder": "Enter surname"
}];

export class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = inputs.reduce((acc, input) => {
      return {...acc, [input.name]: ''};
    }, {})
  }

  onChange = (name, value) => {
    this.setState({[name]: value});
  }

  render() {
    const list = inputs.map(input => {
      return (
        <InputText
          value={this.state[input.name]}
          key={input.name}
          type={input.type}
          name={input.name}
          title={input.title}
          className={input.class}
          placeholder={input.placeholder}
          onChange={this.onChange}
      />
      );
    });

    return (
      <form>
        {list}
      </form>
    );
  }
}

Comments

0

Assuming the JSON you want to iterate through is provided in an array you could do something like this. It will create a label and input element for each JSON.

render() {
  return (
    <div className={className}>
      {
      this.props.yourArray.map(value=>(
        <label htmlFor={value.name}>{value.title}</label>
        <input
          onChange={this.changeValue}
          name={value.name}
          type={value.type}
          value={value.value}
        />
      ))
      }
    </div>
  );
}

Comments

Your Answer

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.