Skip to content

Commit

Permalink
fix(clerk-react): Fix issue with useCustomPages when making changes o…
Browse files Browse the repository at this point in the history
…n the custom pages in dev
  • Loading branch information
anagstef committed Oct 3, 2023
1 parent aef4cc9 commit 19a6314
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 42 deletions.
5 changes: 4 additions & 1 deletion packages/react/src/components/uiComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ class Portal extends React.PureComponent<MountProps> {
private portalRef = React.createRef<HTMLDivElement>();

componentDidUpdate(prevProps: Readonly<MountProps>) {
if (prevProps.props.appearance !== this.props.props.appearance) {
if (
prevProps.props.appearance !== this.props.props.appearance ||
prevProps.props?.customPages?.length !== this.props.props?.customPages?.length
) {
this.props.updateProps({ node: this.portalRef.current, props: this.props.props });
}
}
Expand Down
40 changes: 26 additions & 14 deletions packages/react/src/utils/useCustomElementPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import React, { useState } from 'react';
import { createPortal } from 'react-dom';

export type UseCustomElementPortalParams = {
component: React.ReactNode;
id: number;
};

export type UseCustomElementPortalReturn = {
portal: () => JSX.Element;
mount: (node: Element) => void;
unmount: () => void;
id: number;
};

// This function takes a component as prop, and returns functions that mount and unmount
// the given component into a given node
export const useCustomElementPortal = (elements: UseCustomElementPortalParams[]) => {
const [nodes, setNodes] = useState<(Element | null)[]>(Array(elements.length).fill(null));

export const useCustomElementPortal = (component: JSX.Element) => {
const [node, setNode] = useState<Element | null>(null);

const mount = (node: Element) => {
setNode(node);
};
const unmount = () => {
setNode(null);
};
const portals: UseCustomElementPortalReturn[] = [];

// If mount has been called, CustomElementPortal returns a portal that renders `component`
// into the passed node
elements.forEach((el, index) => {
const mount = (node: Element) => {
setNodes(prevState => prevState.map((n, i) => (i === index ? node : n)));
};
const unmount = () => {
setNodes(prevState => prevState.map((n, i) => (i === index ? null : n)));
};

// Otherwise, CustomElementPortal returns nothing
const CustomElementPortal = () => <>{node ? createPortal(component, node) : null}</>;
const portal = () => <>{nodes[index] ? createPortal(el.component, nodes[index] as Element) : null}</>;
portals.push({ portal, mount, unmount, id: el.id });
});

return { CustomElementPortal, mount, unmount };
return portals;
};
94 changes: 67 additions & 27 deletions packages/react/src/utils/useCustomPages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import React from 'react';

import { UserProfileLink, UserProfilePage } from '../components/uiComponents';
import { customPagesIngoredComponent, userProfileLinkWrongProps, userProfilePageWrongProps } from '../errors';
import type { UserProfilePageProps } from '../types';
import type { UseCustomElementPortalParams, UseCustomElementPortalReturn } from './useCustomElementPortal';
import { useCustomElementPortal } from './useCustomElementPortal';

const errorInDevMode = (message: string) => {
Expand All @@ -21,12 +23,16 @@ const isLinkComponent = (v: any): v is React.ReactNode => {
return !!v && React.isValidElement(v) && (v as React.ReactElement)?.type === UserProfileLink;
};

type CustomPageWithIdType = UserProfilePageProps & { children?: React.ReactNode };

export const useCustomPages = (userProfileChildren: React.ReactNode | React.ReactNode[]) => {
const customPages: CustomPage[] = [];
const customPagesPortals: React.ComponentType[] = [];
const validUserProfileChildren: CustomPageWithIdType[] = [];

React.Children.forEach(userProfileChildren, child => {
if (!isPageComponent(child) && !isLinkComponent(child)) {
errorInDevMode(customPagesIngoredComponent);
if (child) {
errorInDevMode(customPagesIngoredComponent);
}
return;
}

Expand All @@ -37,25 +43,10 @@ export const useCustomPages = (userProfileChildren: React.ReactNode | React.Reac
if (isPageComponent(child)) {
if (isReorderItem(props)) {
// This is a reordering item
customPages.push({ label });
validUserProfileChildren.push({ label });
} else if (isCustomPage(props)) {
// this is a custom page
const { CustomElementPortal, mount, unmount } = useCustomElementPortal(children);
const {
CustomElementPortal: labelPortal,
mount: mountIcon,
unmount: unmountIcon,
} = useCustomElementPortal(labelIcon);
customPages.push({
url,
label,
mountIcon,
unmountIcon,
mount,
unmount,
});
customPagesPortals.push(CustomElementPortal);
customPagesPortals.push(labelPortal);
validUserProfileChildren.push({ label, labelIcon, children, url });
} else {
errorInDevMode(userProfilePageWrongProps);
return;
Expand All @@ -65,20 +56,69 @@ export const useCustomPages = (userProfileChildren: React.ReactNode | React.Reac
if (isLinkComponent(child)) {
if (isExternalLink(props)) {
// This is an external link
const {
CustomElementPortal: labelPortal,
mount: mountIcon,
unmount: unmountIcon,
} = useCustomElementPortal(labelIcon);
customPages.push({ label, url, mountIcon, unmountIcon });
customPagesPortals.push(labelPortal);
validUserProfileChildren.push({ label, labelIcon, url });
} else {
errorInDevMode(userProfileLinkWrongProps);
return;
}
}
});

const customPageContents: UseCustomElementPortalParams[] = [];
const customPageLabelIcons: UseCustomElementPortalParams[] = [];
const customLinkLabelIcons: UseCustomElementPortalParams[] = [];

validUserProfileChildren.forEach((cp, index) => {
if (isCustomPage(cp)) {
customPageContents.push({ component: cp.children, id: index });
customPageLabelIcons.push({ component: cp.labelIcon, id: index });
return;
}
if (isExternalLink(cp)) {
customLinkLabelIcons.push({ component: cp.labelIcon, id: index });
}
});

const customPageContentsPortals = useCustomElementPortal(customPageContents);
const customPageLabelIconsPortals = useCustomElementPortal(customPageLabelIcons);
const customLinkLabelIconsPortals = useCustomElementPortal(customLinkLabelIcons);

const customPages: CustomPage[] = [];
const customPagesPortals: React.ComponentType[] = [];

validUserProfileChildren.forEach((cp, index) => {
if (isReorderItem(cp)) {
customPages.push({ label: cp.label });
return;
}
if (isCustomPage(cp)) {
const {
portal: contentPortal,
mount,
unmount,
} = customPageContentsPortals.find(p => p.id === index) as UseCustomElementPortalReturn;
const {
portal: labelPortal,
mount: mountIcon,
unmount: unmountIcon,
} = customPageLabelIconsPortals.find(p => p.id === index) as UseCustomElementPortalReturn;
customPages.push({ label: cp.label, url: cp.url, mount, unmount, mountIcon, unmountIcon });
customPagesPortals.push(contentPortal);
customPagesPortals.push(labelPortal);
return;
}
if (isExternalLink(cp)) {
const {
portal: labelPortal,
mount: mountIcon,
unmount: unmountIcon,
} = customLinkLabelIconsPortals.find(p => p.id === index) as UseCustomElementPortalReturn;
customPages.push({ label: cp.label, url: cp.url, mountIcon, unmountIcon });
customPagesPortals.push(labelPortal);
return;
}
});

return { customPages, customPagesPortals };
};

Expand Down

0 comments on commit 19a6314

Please sign in to comment.