React - OnChange handler for dynamic form

Viewed 51

I'm writing a React application for a small business selling furniture on the measure (among other services) as a side project. Its main feature is a page with a dynamic form component (i.e. by clicking on buttons a new set of inputs will appear, representing one of the products sold. Here is what I have so far for the Quote parent component:

const Quote = () => {


const [formFields, setFormFields] = useState ([])


const onClick = (event) => {
    
    let newProduct;
    switch (event.target.name) {
        case "arm_std": 
            newProduct = <StandardWardrobe/>
            break;
        case "curtain":
            newProduct = <Curtain />
            break;
        // more products...

        
    }
    setFormFields([...formFields, newProduct])
}


return (
    <div className='quote'>
        <Sidebar />
        <div className='formContainer'>
            <form onSubmit={onSubmit}>
                <Adress_Form/>
                <div id = 'products' key ={unique_key()}>
                {formFields.map(item => (
                    <div key ={unique_key()}>{item}</div>
                ))}
                </div>
                <input type="submit"></input>
            </form>
            <button
                onClick = {onClick}
                className='button' 
                name="arm_std">Armadio Standard</button>
            <button 
                onClick = {onClick}
                className='button' 
                name="curtain">Tenda</button>
            <button 
                onClick = {onClick}
                className='button'>Armadio Mansardato</button>
            <button 
                onClick = {onClick}
                className='button'>Armadio Scorrevole</button>
        </div>
        
        
    </div>
)
}

Here is the code for one of the products:

const StandardWardrobe = () => {



return (
    <div className="add-form" id="arm_std">
        <div className = "product-header" >
            <label>Armadio standard giessegi</label>
        </div>
        <div className="form-control">
            <label>Posizione Armadio</label>
            <input type = 'text' placeholder = "Posizione Armadio" id="arm_std_position" />
        </div>
        <div className="form-control">
            <label>Modello Armadio</label>
            <input type = 'text' placeholder = "Modello" id ="arm_std_model"/>
        </div>
        <div className="form-control">
            <label>Finitura Fianchi/Ante</label>
            <input type = 'text' placeholder = "Finitura" id ="arm_std_finish"/>
        </div>
        <div className="form-control">
            <label>Larghezza in millimetri</label>
            <input type = 'number' placeholder = "L. in mm" id ="arm_std_width"/>
        </div>
        <div className="form-control">
            <label>Altezza minima in millimetri</label>
            <input type = 'number' placeholder = "H. min in mm" id ="arm_std_height"/>
        </div>
        <div className="form-control">
            <label>Accessori</label>
            <textarea className="form-control textarea"placeholder="Accessori"  id="arm_std_accessories" />
        </div>
        <div className="form-control">
            <label>Note</label>
            <textarea className="form-control textarea"placeholder="Note" id ="arm_std_notes" />
        </div>
        <div className="form-control">
            <label>Prezzo</label>
            <input type = 'number' placeholder = "Prezzo" id="price" />
        </div>
    </div>
)

}

What I'm having trouble with is how do I construct an onChangeHandler capable of storing the inputs of all these different products?

Of course, I'm open to changing the current structure, since this is my first real project using the MERN stack.

1 Answers

Ok guys, after a couple of days I have worked out a solution that meets all my requirements and also satisfies me. Forget my own comment, that solution was bad and would go against everything React stands for. My current solution is the following:

import { useState} from 'react';
import { useDispatch } from 'react-redux';
import { createQuote, getQuotes } from '../../features/quote/quoteSlice';
import unique_key from '../../utils/unique_key';
import Sidebar from '../../components/sidebar/Sidebar'
import './quote.scss'
import InputField from './InputField';
import './form.scss'
import TextArea from './TextArea';


const Quote = () => {


    // To avoid confusion. The indices in the arrays represent:
    // 0: value
    // 1: placeholder
    // 2: label text
    // 3: element type ("input", "textArea", etc.)
    const products = {
        std_ward: {
            std_ward_position: ["","Posizione Armadio", "Posizione Armadio", "input"],
                std_ward_model: ["","Modello", "Modello", "input"],
                std_ward_finish: ["","Finitura", "Finitura Fianchi/Ante", "input"],
                std_ward_width: ["", "L. in mm", "Larghezza in millimetri", "input"],
                std_ward_height: ["", "H. in mm", "Altezza minima in millimetri", "input"],
                std_ward_depth: ["", "P. in mm", "Profondità in millimetri", "input"],
                std_ward_accessories: ["", "Accessori", "Accessori", "textArea"],
                std_ward_notes: ["", "Note", "Note", "textArea"],
                std_ward_price: ["", "Prezzo", "Prezzo", "input"]
            
            
        },
        curtain: {
            curtain_position: ["","Posizione Tenda", "Posizione Tenda", "input"],
                curtain_width: ["", "L. in mm", "Larghezza in millimetri", "input"],
                curtain_height_left: ["", "H. sx in mm", "Altezza sinistra in millimetri", "input"],
                curtain_height_right: ["", "H. dx in mm", "Altezza destra in millimetri", "input"],
                curtain_fabric: ["", "Stoffa", "Stoffa", "input"],
                curtain_sewing: ["", "Cucitura", "Cucitura", "input"],
                curtain_notes: ["", "Note", "Note", "textArea"],
                curtain_price: ["", "Prezzo", "Prezzo", "input"]
            
        }
    }

    const [productFields, setProductFields] = useState([])

    const onClick = (event) => {
        let newProduct 
        switch (event.target.name) {
            case "std_ward":
                newProduct = products.std_ward
                break
            case "curtain":
                newProduct = products.curtain
                break
        }
        setProductFields([...productFields, newProduct])
    }

    const onChangeHandler = (index, event) => {
        let data = [...productFields]
        data[index][event.target.name][0] = event.target.value
        setProductFields(data)
    }
 
    const onSubmit = (event) => {
        event.preventDefault()
    }

    

    return (
        <div className='quote'>
            <Sidebar />
            <div className='formContainer'>
                <form onSubmit={onSubmit}>
                    <Adress_Form/>
                    <div>
                        {productFields.map((item, index) => (
                            <div key= {index}>
                                {Object.entries(item).map(([key, value]) => {
                                    if(value[3] === "input") {
                                        return (
                                            <InputField label={value[2]} placeholder={value[1]} key={key} name={key} value={value[0]} onChange={event => onChangeHandler(index, event)}/>
                                        )
                                    }else if(value[3] === "textArea") {
                                        return (
                                            <TextArea label={value[2]} placeholder={value[1]} key={key} name={key} value={value[0]} onChange={event => onChangeHandler(index, event)}/>
                                        )
                                    }
                                    
                                })}
                                </div>
                            
                            
                        ))}
                    </div>
                    
                    <input type="submit"></input>
                </form>
                <button
                    onClick = {onClick}
                    className='button' 
                    name="std_ward">Armadio Standard</button>
                <button 
                    onClick = {onClick}
                    className='button' 
                    name="curtain">Tenda</button>
                <button 
                    onClick = {onClick}
                    className='button'>Armadio Mansardato</button>
                <button 
                    onClick = {onClick}
                    className='button'>Armadio Scorrevole</button>
            </div>
            
            
        </div>
    )
}
export default Quote

As you can see, there is no more need for all the products .jsx files. Now they are all defined inside a plain JS object. The last things I need to create are custom element types (input, textarea, and so on...) and pass down props to them.

import './form.scss'

const InputField = ({label,...props}) => {
  return (
    <div className='form-control'>
        <label>{label}</label>
        <input {...props}/>
    </div>
  )
}

export default InputField

import './form.scss'

const TextArea = ({label,...props}) => {
  return (
    <div className='form-control'>
        <label>{label}</label>
        <textarea {...props}/>
    </div>
  )
}

export default TextArea

So that is it. Would any of you guys give a feedback on this solution? As I said in the first post, this is my first real world project using React.

Related