Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react2angular fails with: renderWithSharedContext is not a function #11

Open
mgenov opened this issue Jul 3, 2023 · 2 comments
Open

Comments

@mgenov
Copy link

mgenov commented Jul 3, 2023

Due some reason, the react2angular initially fails with renderWithSharedContext.

Here is the snippet that initializes the react2angular-shared-context:

import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import createSharedContext from 'react2angular-shared-context'

type Props = {
  children: React.ReactNode
}
const Router = ({ children }: Props) => {
  return <BrowserRouter>{children}</BrowserRouter>
}

export const sharedContext = createSharedContext(Router)

Here is the trace:

Uncaught TypeError: renderWithSharedContext is not a function
    UseSharedContext Angular
    React 5
    unstable_runWithPriority scheduler.development.js:468
    React 4
    unstable_runWithPriority scheduler.development.js:468
    React 7
    tsx index.bss.tsx:52
    factory react refresh:6
    Webpack 9
@emandres
Copy link

emandres commented Aug 7, 2023

I ran into this same error. I think the trick is to add the component somewhere in your application. Without it, it never creates an instance of the shared context.

@mgenov
Copy link
Author

mgenov commented Aug 7, 2023

Yes, adding the component and fixing the issue. Here is the complete code with some typescript alignments:

import React, {
  ComponentProps,
  ComponentType,
  Fragment,
  FunctionComponent,
  useEffect,
  useLayoutEffect,
  useRef,
  useState
} from 'react'
import { createPortal } from 'react-dom'
import { v4 as uuid } from 'uuid'

interface IComponent {
  key: string
  node: HTMLElement
  component: ComponentType<any>
  props: any
}

interface IComponentInstance<T> {
  update: (props: T) => void
  remove: () => void
}

const createSharedContext = (Root: ComponentType<any> = Fragment, isAsync = true) => {
  // Make method accessible by both SharedContext and withSharedContext
  let renderWithSharedContext: {
    (component: IComponent): IComponentInstance<ComponentProps<typeof component.component>>
  } = () => ({
    update: () => {},
    remove: () => {}
  })

  const SharedContext: FunctionComponent<ComponentProps<typeof Root>> = rootProps => {
    // List of components and their props to be rendered with react portal in their designated nodes
    const [components, setComponents] = useState<Array<IComponent | undefined>>([])

    useEffect(() => {
      renderWithSharedContext = (component: IComponent) => {
        // Add component to list
        setComponents(prevState => {
          prevState.push(component)
          return [...prevState]
        })

        // Return callbacks to update and remove component from list
        return {
          update: props => {
            setComponents(prevState => {
              const prevComponent = prevState.find(c => c?.key === component.key)
              if (!prevComponent) {
                return prevState
              }
              prevComponent.props = props
              return [...prevState]
            })
          },
          remove: () => {
            setComponents(prevState => {
              const index = prevState.findIndex(c => c?.key === component.key)
              if (index === -1) {
                return prevState
              }
              prevState[index] = undefined
              return [...prevState]
            })
          }
        }
      }
    }, [])

    // Return list of react portals wrapped in one or multiple providers
    return (
      <Root {...rootProps}>
        {components.map(component => {
          if (!component) {
            return null
          }
          const { key, node, component: C, props } = component
          return createPortal(<C key={key} {...props} />, node)
        })}
      </Root>
    )
  }

  const useSharedContext: (component: ComponentType<any>) => FunctionComponent<any> = component => {
    // Create as local variable instead of returning inline to fix TSLint
    const UseSharedContext: FunctionComponent<ComponentProps<typeof component>> = props => {
      // Create unique key for this instance
      const key = useRef(uuid())
      // Hold reference to rendered hidden DOM node
      const ref = useRef<HTMLDivElement>(null)
      // Instance is SharedContext
      const instance = useRef<IComponentInstance<ComponentProps<typeof component>>>()

      ;(isAsync ? useEffect : useLayoutEffect)(() => {
        if (instance.current) {
          // Pass prop updates to instance in SharedContext
          instance.current.update(props)
        }
      }, [props])
      ;(isAsync ? useEffect : useLayoutEffect)(() => {
        if (ref.current && ref.current.parentElement) {
          // Create instance in SharedContext
          instance.current = renderWithSharedContext({
            key: key.current,
            node: ref.current.parentElement,
            component,
            props
          })

          // Return callback to unmount component in SharedContext when this component is unmounted
          return instance.current.remove
        }
        return
      }, [])

      // Hidden <div> component only used to get reference in dom
      return <div ref={ref} style={{ display: 'none' }} />
    }

    return UseSharedContext
  }

  return {
    component: SharedContext,
    use: useSharedContext
  }
}

export default createSharedContext

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants