Skip to content

Instantly share code, notes, and snippets.

@Grohden
Last active February 27, 2025 14:03
Show Gist options
  • Save Grohden/b922933eea8b43efe443dd241d985ec0 to your computer and use it in GitHub Desktop.
Save Grohden/b922933eea8b43efe443dd241d985ec0 to your computer and use it in GitHub Desktop.
A way to render a view (like a portal) and get a result out of it (similar to react-native-use-modal)
import {
Fragment,
type ReactNode,
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { v4 } from 'uuid';
type RenderParams<P, O> = {
confirm: (data: O) => void;
cancel: () => void;
props: P;
};
type ContextType = {
addNode: (groupId: string, node: JSX.Element) => string;
removeNode: (id: string) => void;
removeNodeGroup: (groupId: string) => void;
};
type ManagedView = {
id: string;
groupId: string;
node: JSX.Element;
};
type ViewResult<T> = { type: 'success'; data: T } | { type: 'cancel' };
const Context = createContext<ContextType | null>(null);
export const useViewResult = <P, O>(props: {
render: (props: RenderParams<P, O>) => JSX.Element;
}) => {
const viewManger = useContext(Context)!;
const groupId = useMemo(() => v4(), []);
useEffect(() => {
return () => viewManger.removeNodeGroup(groupId);
}, [viewManger.removeNodeGroup, groupId]);
return useCallback(
(params: P) => {
return new Promise<ViewResult<O>>((resolve, reject) => {
const id = viewManger.addNode(
groupId,
props.render({
props: params,
confirm: (data) => {
viewManger.removeNode(id);
resolve({ type: 'success', data });
},
cancel: () => {
viewManger.removeNode(id);
reject({ type: 'cancel' });
},
}),
);
});
},
[props.render, viewManger, groupId],
);
};
export const ManagedViewRenderer = ({ children }: { children: ReactNode }) => {
const [nodes, setNodes] = useState<ManagedView[]>([]);
const value: ContextType = useMemo(
() => ({
addNode: (groupId, node) => {
const id = v4();
setNodes((prev) => [...prev, { id, groupId, node }]);
return id;
},
removeNode: (id) => {
setNodes((prev) => prev.filter((node) => node.id !== id));
},
removeNodeGroup: (groupId) => {
setNodes((prev) => prev.filter((node) => node.groupId !== groupId));
},
}),
[],
);
return (
<Context.Provider value={value}>
{children}
{nodes.map((view) => (
<Fragment key={view.id}>{view.node}</Fragment>
))}
</Context.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment