skip to Main Content

I have a react-component that acts as a container for multiple text-fields. Initially there’s just a single textbox, however the user can dynamically add more.

So I have a model for every textfield:

interface TextFieldProps
{
    value: string,
    setValue: (newValue: string) => void
}

which is used in my MyTextField-component:

function MyTextField(props: TextFieldProps) => {
    return (<TextField value={props.value} onChange={props.setValue} />)
}

As you can see MyTextField is just a wrapper around a MUI-textbox. When the onChange-event on that textbox is triggered, the setValue-function passed to my wrapper is executed. This way I can grab the textbox’s values inside my cntainer and also call some code whenever one of the textboxes changes.

So finally here’s the Container-component

function Container() => {
    const[textFields, setTextFields] = useState<TextFieldProps>[]();

    return(
        <>
            { 
                textFields.map(t => <MyTextField value={ t.value } setValue={ t.setValue } />
            }
        </>
    )
}

However I have no idea how to create an instance of the TextFieldProps in order to build new wrapper-components from them.

What I want to achieve is have my container access to the containing textbox-values and their setter-functions.

2

Answers


  1. To achieve this, you can manage the state of text fields as an array of objects in your Container component. Each object will have a value and an id to uniquely identify each text field (for example, the date created, or the index, or the guuid, whatever), ensuring the state updates independently. Please take a look at sandbox here

    Define the Text Field Component

    Create a wrapper around the MUI TextField that takes value and setValue as props.

    // MyTextField.tsx
    import React from 'react';
    import { TextField } from '@mui/material';
    
    interface TextFieldProps {
        value: string;
        setValue: (newValue: string) => void;
    }
    
    function MyTextField({ value, setValue }: TextFieldProps) {
        return (
            <TextField
                value={value}
                onChange={(e) => setValue(e.target.value)}
                variant="outlined"
                fullWidth
                margin="normal"
            />
        );
    }
    
    export default MyTextField;
    

    Define the Container Component

    In the Container component, manage the state as an array of objects. Each object will hold a value and an id.

    // Container.tsx
    import React, { useState } from 'react';
    import MyTextField from './MyTextField';
    
    interface TextFieldData {
        value: string;
        id: number;
    }
    
    function Container() {
        // State to hold the array of text field data
        const [textFields, setTextFields] = useState<TextFieldData[]>([
            { value: '', id: Date.now() }, // Initial text field
        ]);
    
        // Function to add a new text field
        const addTextField = () => {
            setTextFields((prevFields) => {
                const updatedFields = [
                    ...prevFields,
                    { value: '', id: Date.now() }, // Add a new text field with a unique ID
                ];
                console.log('After adding new field:', updatedFields); // Log the state after adding a field
                return updatedFields;
            });
        };
    
        // Function to update a specific text field's value
        const updateTextField = (id: number, newValue: string) => {
            setTextFields((prevFields) => {
                const updatedFields = prevFields.map((field) =>
                    field.id === id ? { ...field, value: newValue } : field
                );
                console.log('After updating field:', updatedFields); // Log the state after updating a field
                return updatedFields;
            });
        };
    
        return (
            <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
                {textFields.map((field) => (
                    <MyTextField
                        key={field.id}
                        value={field.value}
                        setValue={(newValue) => updateTextField(field.id, newValue)}
                    />
                ))}
                <button onClick={addTextField} style={{ marginTop: '10px' }}>
                    Add Text Field
                </button>
            </div>
        );
    }
    
    export default Container;
    

    Explanation

    1. The state (textFields) is an array of objects where each object has
      a value and a unique id. The id ensures each text field is
      independently managed.
    2. The addTextField function adds a new object to the state array.
    3. The updateTextField function updates a specific text field’s value
      using its id.
    Login or Signup to reply.
  2. In order to control multiple textboxes at a time, one way to do it is to have a useState in which you have a map where key is a unique key of a text field and value is the value of the input.

    Then, you can use your array of textboxes (eg. ['firstName', 'lastName', 'address1', 'address2']) and map the MyTextField retrieving text field value by its key and setter function by the makeHandleChange factory.

    This way, you can easily access textboxes’ values and adjust the factory function to your business logic.

    type Values = Record<string, string>;
    
    function Container() => {
      const [textFields, setTextFields] = useState<string[]>([]);
      const [values, setValues] = useState<Values>({});
    
      const makeHandleChange = (key: string) => (e: ChangeEvent<HTMLInputElement>) => {
        setValues(prevValues => ({ ...prevValues, [key]: e.target.value }))
      }
    
      return (<>
        {
          textFields.map(textFieldKey => <MyTextField key={textFieldKey} value={values[textFieldKey} setValue={makeHandleChange(textFieldKey)} />
         } </>
        )
      }

    Without makeHandleChange factory:

    type Values = Record<string, string>;
    
    function Container() => {
      const [textFields, setTextFields] = useState<string[]>([]);
      const [values, setValues] = useState<Values>({});
    
      const handleChange = (key: string, e: ChangeEvent<HTMLInputElement>) => {
        setValues(prevValues => ({ ...prevValues, [key]: e.target.value }))
      }
    
      return (<>
        {
          textFields.map(textFieldKey => <MyTextField key={textFieldKey} value={values[textFieldKey} setValue={(e) => handleChange(textFieldKey, e)} />
         } </>
        )
      }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search