javascriptreactjsfunctional-programming

How to build a good data structure for a react state, that allows me to easily update deeply nested array of objects?


I am trying to implement a property (real estate) chain

Each chain item should know which chain item is above/below it

A chain item can have its own sub-chain, and this in turn applies to its sub-chain items as well and so on

Visually like this

[
{
id: 1,
belowId:0
},
{
id: 0,
belowId:2,
aboveId:1,
subChain: [{ id:3, subChain: [{ id:4}] }]
},
{
id: 2,
aboveId:0
}
]

I tried this above structure but I started getting confused how to update nested items in state and ui


Solution

  • As no one has answered my question, maybe I wasn't clear enough too to be fair. I will answer it here so that if someone stumbles on a similar problem.

    So the main idea is that you want to flatten the array and make sure that subChain only consist of ids not references.

    Here is how the ChainItem look like (state structure)

    type ChainItem = {
        id: string;
        root: boolean;
        subChain: string[];
        aboveId?: string;
        belowId?: string;
        parentId?: string;
    };
    

    I create an item with this utility method

    const createItem = (id: number, root: boolean = false): ChainItem => {
        return { id: `${id}`, root, aboveId: null, belowId: null, parentId: null, subChain: [] };
    };
    

    How it looks in the UI

    interface ChainTreeItem extends Omit<ChainItem, "subChain"> {
        subChain: ChainTreeItem[];
    }
    

    I build the UI tree like this

    const buildTree = (chain: ChainItem[], id: string) => {
        const itemMap = new Map(chain.map(item => [item.id, item]));
        const build = (id: string): ChainTreeItem | null => {
            const item = itemMap.get(id);
            if (!item) return null;
    
            return {
                ...item,
                subChain: item.subChain.map(subId => build(subId))
            };
        };
    
        return build(id);
    };
    

    React state

    const [chain, setChain] = useState<ChainItem[]>([createItem(0, true)]);
    const chainTree: ChainTreeItem[] = useMemo(() => {
        return chain.filter(property => property.root).map(property => buildTree(chain, property.id));
    }, [chain]);
    

    Create a sibling

    const addSibling = useCallback((id: string, position: "above" | "below") => {
            const isAddAbove = position === "above";
            setChain(prev => {
                let parent: PropertyItem | null = null;
                const newItem = createItem(prev.length);
                let updated = prev.map(item => {
                    if (item.id === id) {
                        parent = prev.find(itm => itm.id === item.parentId);
                        if (isAddAbove) {
                            newItem.belowId = item.id;
                            return { ...item, aboveId: newItem.id };
                        } else {
                            newItem.aboveId = item.id;
                            return { ...item, belowId: newItem.id };
                        }
                    }
                    return item;
                });
                if (parent) {
                    newItem.parentId = parent.id;
                    updated = updated.map(item => {
                        if (item.id === parent.id) {
                            return {
                                ...item,
                                subChain: isAddAbove ? [newItem.id, ...item.subChain] : [...item.subChain, newItem.id]
                            };
                        }
                        return item;
                    });
                } else {
                    newItem.root = true;
                }
                return isAddAbove ? [newItem, ...updated] : [...updated, newItem];
            });
        }, []);
    

    Create a sub chain

     const addRight = useCallback((id: string) => {
            setChain(prev => {
                const newItem = createItem(prev.length);
                const updated = prev.map(item => {
                    if (item.id === id) {
                        newItem.parentId = item.id;
                        return { ...item, subChain: [...item.subChain, newItem.id] };
                    }
                    return item;
                });
                return [...updated, newItem];
            });
        }, []);