Search code examples
react-hooksnestedsetstatereact-statereact-state-management

React Form UI Not Reflecting State Changes Correctly


I'm working on a nested form in React where a user can have multiple forms, each form can have multiple sections, and each section can have multiple dropdowns. I'm facing an issue where removing a specific dropdown updates the state correctly but does not reflect the correct dropdown being removed in the UI.

I have these States

const [addConditions, setAddConditions] = useState([[]]);
const {formIndex} = useContext(FormContext);
const [addConditions, setAddConditions] = useState([[]]);
const { formIndex } = useContext(FormContext);
const [eventValues, setEventValues] = useState([{
    // ...other properties
    conditions: [{ locked: [] }],
    // ...
}]);

To add a section

const addSection = () => {
    setAddConditions(prev => [...prev, [""]])
}

To add a dropdown

const addDropdownToSection = (sectionIndex) => {
    setAddConditions(prev => {
        const updated = [...prev];
        updated[sectionIndex].push("");
        return updated;
    });
}

I store dropdown values in eventValues

const handlePropertyInputChange = (outerIndex, innerIndex, stateType="locked", e) => {
        setEventValues(prevValues => {
            let updatedEvents = [...prevValues];
            let updatedConditions = [...updatedEvents[formIndex].conditions];

            if (!updatedConditions[outerIndex]) {
                updatedConditions[outerIndex] = { locked: [] };
            }
            const { name, value } = extractValue(e);
            updatedConditions[outerIndex][stateType][innerIndex] = {
                ...updatedConditions[outerIndex][stateType][innerIndex],
                [name]: value,
            };
            updatedEvents[formIndex].conditions = updatedConditions;
            return updatedEvents;
        });
    };

Problem arises when I have to remove a specific dropdown

const removePropertyFromCondition = (sectionIndex, propertyIndex) => {
    setEventValues(prevValues => {
        let updateEvents = makeDeepCopy(prevValues)
        let updatedConditions = [...updateEvents[formIndex].conditions];
        updatedConditions[sectionIndex].locked.splice(propertyIndex, 1);
        updateEvents[formIndex].conditions = updatedConditions;
        console.log({updateEvents})
        return updateEvents
    });
    setAddConditions(prev => {
        const updated = makeDeepCopy(prev)
        updated[sectionIndex].splice(propertyIndex, 1);
        return updated;
    });
};

When invoking removePropertyFromCondition, the eventValues state is updated correctly (as seen in the console), but in the UI, it's always the last dropdown in the section that gets removed, not the one I intended. After this, any interaction with dropdown values results in the app crashing.

How can I ensure that the correct dropdown is removed in the UI?

I attempted to assign a unique identifier to each section to address the issue, but this approach did not resolve the problem.

Any help is appreciated, thanks


Solution

  • Issue was arising due to unstable keys. I was using array indexes as keys for the mapped <div/> but they were changing when elements were being added or removed. I used UUID's as stable identifiers.

    {addConditions.map((propertyConditions, outerIndex) => {
        return (
            <div key={propertyConditions.UUID}>
                {propertyConditions.map((propertyCondition, innerIndex) => {
                    return (
                        <div key={propertyCondition.UUID}>
                        </div>
                    )
                })}
            </div>
        )
    })}