0

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>
  );
};

1 Answer 1

1

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.

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

7 Comments

Yeah I see what you mean, the problem is Display has to be generic, so I can't set the keys ahead of time. It needs to dynamically read them from whatever fragments it's given.
This is a bit chicken before the egg kind of deal, it would be easy to make it generic if you have some way of knowing how many fragments there will be or some way to generate the list of ID's before you try to create the list of fragments.
More or less, if you're going to do have data flow down from the parent to the child, you can't have the fragment coming up with it's own ID, it needs to be the other way around.
I'm not in love with a particular id, if they need to be set by the parent that's fine. Could you show how to implement?
Reading more into where your error is coming from, can I ask what testing framework you're using that's causing you to need to predefine these fragments? I wasn't exactly on the mark with what your issue was, but React's composition model makes doing what you're asking a little difficult.
|

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.