-
-
Save ManUtopiK/05027f7186fc174bb420484174d56a59 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint-disable @typescript-eslint/no-explicit-any */ | |
| import type { Component } from "vue"; | |
| import { | |
| memo, | |
| useEffect, | |
| useLayoutEffect, | |
| useMemo, | |
| useRef, | |
| useState, | |
| useCallback, | |
| } from "react"; | |
| import { useGetContexts } from "./react-contexts/use-get-contexts"; | |
| import Vue, { provide, ref } from "vue"; | |
| /** | |
| * TLDR React doesn't handle errors thrown async | |
| * So this makes async errors thrown in Vue sync React errors so we can handle them better | |
| * | |
| * @see https://github.com/facebook/react/issues/11409#issuecomment-783309449 | |
| */ | |
| export function useCrash() { | |
| const [, setState] = useState(); | |
| return useCallback( | |
| (err) => | |
| setState(() => { | |
| throw err; | |
| }), | |
| [], | |
| ); | |
| } | |
| interface VueReactRendererProps extends Record<string, any> { | |
| component: Component; | |
| } | |
| function VueRendererBase({ component, ...props }: VueReactRendererProps) { | |
| const crash = useCrash(); | |
| const propsRef = useRef(props); | |
| const contexts = useGetContexts(); | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| const contextsRef = useMemo(() => ref(contexts), []); | |
| const v = useMemo(() => { | |
| return new Vue({ | |
| setup() { | |
| provide("contexts", contextsRef); | |
| }, | |
| render: function (h) { | |
| const eventBinds = Object.entries(propsRef.current).reduce( | |
| (prev, [propName, propValue]) => { | |
| const isEvent = /on[A-Z]/.exec(propName); | |
| if (!isEvent) return prev; | |
| // Remove `on` and lowercase only first letter | |
| const uppercaseEventName = propName.slice(2); | |
| /** | |
| * We need to bind both `onEvent` and `on-event` to the same function, | |
| * as React doesn't easily handle binding `-` to JSX props AFAIK | |
| */ | |
| // someEvent | |
| const simpleEventName = | |
| uppercaseEventName.charAt(0).toLowerCase() + | |
| uppercaseEventName.slice(1); | |
| // some-event | |
| const dashCaseEventName = simpleEventName.replace( | |
| /[A-Z]/g, | |
| (letter) => { | |
| return `-${letter.toLowerCase()}`; | |
| }, | |
| ); | |
| prev[simpleEventName] = propValue; | |
| prev[dashCaseEventName] = propValue; | |
| return prev; | |
| }, | |
| {} as Record<string, Function>, | |
| ); | |
| return h(component, { | |
| props: propsRef.current, | |
| on: eventBinds, | |
| }); | |
| }, | |
| errorCaptured: (err) => { | |
| // Ignore network request failures | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| if ((err as any).response) return; | |
| crash(err); | |
| }, | |
| }); | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, []); | |
| useEffect(() => { | |
| contextsRef.value = contexts; | |
| }, [contexts]); | |
| const containerRef = useRef<HTMLDivElement>(null); | |
| const vueContainerRef = useRef<HTMLDivElement | null>(null); | |
| const [vm, setVm] = useState(null as any); | |
| useEffect(() => { | |
| if (!vm) return; | |
| propsRef.current = props; | |
| vm.$forceUpdate(); | |
| }, [vm, props]); | |
| useLayoutEffect(() => { | |
| return () => { | |
| if (!vm) return; | |
| vm.$destroy(); | |
| if ( | |
| vueContainerRef.current && | |
| containerRef.current && | |
| containerRef.current.contains(vueContainerRef.current) | |
| ) { | |
| containerRef.current.removeChild(vueContainerRef.current); | |
| } | |
| vueContainerRef.current = null; | |
| }; | |
| }, [vm]); | |
| return ( | |
| <div | |
| style={{ display: "contents" }} | |
| ref={(el) => { | |
| containerRef.current = el; | |
| if (!el) return; | |
| if (vm) return; | |
| // Create a separate container for Vue to manage | |
| const vueContainer = document.createElement("div"); | |
| vueContainer.style.display = "contents"; | |
| el.appendChild(vueContainer); | |
| vueContainerRef.current = vueContainer; | |
| setVm(v.$mount(vueContainer)); | |
| }} | |
| /> | |
| ); | |
| } | |
| export const VueRenderer = memo(VueRendererBase); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment