Skip to content

Dynamic input value in React

So I have a fragment factory being passed into a Display component. The fragments have input elements. Inside Display I have an onChange handler that takes the value of the inputs and stores it in contentData[e.target.id]. This works, but switching which fragment is displayed erases their values and I’d rather it didn’t. So I’m trying to set their value by passing in the state object to the factory. I’m doing it in this convoluted way to accomodate my testing framework. I need the fragments to be defined outside of any component and passed in to Display as props, and I need them all to share a state object.

My problem is setting the value. I can pass in the state object (contentData), but to make sure the value goes to the right key in the contentData data object I’m trying to hardcode it with the input’s id. Except contentData doesn’t exist where the fragments are defined, so I get an error about not being able to reference a particular key on an undefined dataObj.

I need to find a way to set the input values to contentData[e.target.id]. Thanks.

File where fragments are defined. Sadly not a component.

const fragments = (onChangeHandler, dataObj) => [
    <Fragment key="1">
        <input 
            type="text" 
            id="screen1_input1" 
            onChange={onChangeHandler}
            value={dataObj['screen1_input1']} // this doesn't work
        />
        one
    </Fragment>,
    <Fragment key="2">
        <input 
            type="text" 
            id="screen2_input1" 
            onChange={onChangeHandler}
            value={dataObj['screen2_input1']}
        />
        two
    </Fragment>
]

Display.js

const Display = ({ index, fragments }) => {
    const [contentData, setContentData] = useState({})
    
    const onChange = e => {     
        // set data
        const newData = {
            ...contentData,
            [e.target.id]: e.target.value
        }
        setContentData(newData)
    };

  return (
      <Fragment>{fragments(onChange, contentData)[index]}</Fragment>
  );
};

Answer

After conversing with you I decided to rework my response. The problem is mostly around the implementation others might provide in these arbitrary fragments.

You’ve said that you can define what props are passed in without restriction, that helps, what we need to do is take in these nodes that they pass in, and overwrite their onChange with ours, along with the value:

const RecursiveWrapper = props => {
    const wrappedChildren = React.Children.map(
        props.children,
        child => {
            if (child.props) {
                return React.cloneElement(
                    child,
                    {
                        ...child.props,
                        onChange: props.ids.includes(child.props.id) ? child.props.onChange ? (e) => {
                          child.props.onChange(e);
                          props.onChange(e);
                        } : props.onChange : child.props.onChange,
                        value: props.contentData[child.props.id] !== undefined ? props.contentData[child.props.id] : child.props.value,
                    },
                    child.props.children 
                    ? (
                        <RecursiveWrapper 
                          ids={props.ids} 
                          onChange={props.onChange} 
                          contentData={props.contentData}
                        >
                          {child.props.children}
                        </RecursiveWrapper>
                      ) 
                    : undefined
                )
            }
            return child
        }
    )
    return (
        <React.Fragment>
            {wrappedChildren}
        </React.Fragment>
    )
}

const Display = ({ index, fragments, fragmentIDs }) => {
    const [contentData, setContentData] = useState(fragmentIDs.reduce((acc, id) => ({ 
...acc, [id]: '' }), {}));
    
    const onChange = e => {     
        setContentData({
            ...contentData,
            [e.target.id]: e.target.value
        })
    };

  const newChildren = fragments.map(fragment => <RecursiveWrapper onChange={onChange} ids={fragmentIDs} contentData={contentData}>{fragment}</RecursiveWrapper>);

  return newChildren[index];
};

This code outlines the general idea. Here we are treating fragments like it is an array of nodes, not a function that produces them. Then we are taking fragments and mapping over it, and replacing the old nodes with nodes containing our desired props. Then we render them as planned.