Skip to content

Instantly share code, notes, and snippets.

@Grohden
Last active July 23, 2025 21:05
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 {
createContext,
Fragment,
type ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { v4 } from 'uuid';
type RenderParams<P, O> = {
confirm: (data: O) => void;
props: P;
};
type ContextType = {
addNode: (groupId: string, node: ReactNode) => string;
removeNode: (id: string) => void;
removeNodeGroup: (groupId: string) => void;
};
type ManagedView = {
id: string;
groupId: string;
node: ReactNode;
};
const Context = createContext<ContextType | null>(null);
const useRequiredContext = () => {
const context = useContext(Context);
if (!context) {
throw new Error('useViewResult must be used within a ManagedViewRenderer');
}
return context;
};
export const useViewResult = <P, O>(props: {
render: (props: RenderParams<P, O>) => ReactNode;
}) => {
const viewManger = useRequiredContext();
const groupId = useMemo(() => v4(), []);
useEffect(() => {
return () => viewManger.removeNodeGroup(groupId);
}, [viewManger.removeNodeGroup, groupId]);
return useCallback(
(params: P) => {
return new Promise<O>(resolve => {
const id = viewManger.addNode(
groupId,
props.render({
props: params,
confirm: data => {
viewManger.removeNode(id);
resolve(data);
},
})
);
});
},
[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>
);
};
@Grohden
Copy link
Author

Grohden commented Jul 2, 2025

Usage example:

// declare render
export const useDefaultConfirmation = () =>
  useViewResult({
    render: useCallback(({ props, confirm }) => {
      return (
         <View>
            <Button onPress={() => confirm()}>Confirm!</Button>
         </View>
      );
    }, []),
  });

// use
const MyView = () => {
   const confirm = useDefaultConfirmation();
   
   const onPress = () => {
    const result = await confirm();

    Alert.alert(JSON.stringify(result, null, 2));
  }

   return (
    <View>
      <Button onPress={onPress}>
        Press me!
      </Button>
    </View>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment