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
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];
});
}, []);