I need to write a nested list component, which will render a list of names with a button to show children if present,
and apply the same logic recursively to child components,
(No libraries)
Json example :
[
{
"name": "Rosetta",
"lastName": "Bins",
"children": [
{
"name": "Tristian",
"lastName": "Boehm",
"children": [
{
"name": "Ulises",
"lastName": "Mueller",
"children": [
{ "name": "Carlo", "lastName": "Hagenes", "hasChildren": false },
{
"name": "Mozelle",
"lastName": "Bauch",
"children": [
{
"name": "Roxane",
"lastName": "Sauer",
"hasChildren": false
},
...
The catch is for child components to remember the state they’re in, if I open the child list and then collapse the parent list, child list should still be open next time.
I’ve tried the naive approach of keeping a map of all objects i’ve opened by giving them isOpen property and rendering conditionally, but I couldn’t figure out how to prevent re-renders since all components depend on the map which has a new pointer each render.
My friend advised me to try using react context API, which i did:
export const UserContext = createContext<UserContextType>({
userStatus: {},
toggleOpen: () => { },
})
export const UserContextProvider = ({ children }) => {
const [userStatus, setUserStatus] = useState<UserListStateType>({});
const toggleOpen = useCallback(
(id: string) => {
setUserStatus(prev => ({
...prev,
[id]: {
...prev[id],
isOpen: prev[id] ? !prev[id].isOpen : true,
}
}));
}, []);
return <UserContext.Provider value={{ userStatus, toggleOpen }}> {children} </UserContext.Provider>
}
And then shared the context across my User components
const _User: FC<UserPropsType> = ({
user: { name, lastName, hasChildren, children },
level,
id,
}) => {
const { userStatus, toggleOpen } = useContext(UserContext);
const [isOpen, setOpen] = useState(false)
useEffect(() => {
setOpen(userStatus[id]?.isOpen ?? false);
}, [userStatus]);
let isLoading = false
return (
<div style={{ marginLeft: level * 20 }}>
<div>
{name} {lastName}
{hasChildren && (
<button disabled={isLoading} onClick={() => toggleOpen(id)}>
{isOpen ? "Hide children" : "Show children"}
</button>
)}
</div>
{isLoading ? <> Loading ...</> : isOpen && children?.map(item => (
<User
key={`${item.name}--${item.lastName}`}
id={`${item.name}--${item.lastName}`}
user={item}
userStatus={userStatus}
level={level + 1}
/>
))}
</div>
)
}
I also tried configuring memo to somehow memoize it
export const User = memo(_User
// , (prev, next) => {
// const { id } = next;
// const { isLoading: isLoadingNew, isOpen: isOpenNew } = next.userStatus[id];
// const { isLoading: isLoadingOld, isOpen: isOpenOld } = prev.userStatus[id];
// return isLoadingNew !== isLoadingOld || isOpenOld !== isOpenNew
// }
);
But I still cant wrap my head around how to memoize the whole list while keeping tabs on which are collapsed and which are open, since I have to depend on the state of the map
Please assist, thank you in advance