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

Convert the React bootloader to TypeScript #5961

Merged
merged 13 commits into from
Aug 29, 2023
Merged

Conversation

dem4ron
Copy link
Member

@dem4ron dem4ron commented Aug 22, 2023

This PR turns react-bootloader.jsx into react-bootloader.tsx in utils.

@dem4ron dem4ron marked this pull request as ready for review August 23, 2023 11:03
type GeneratorFunc = (data: any, elem: HTMLElement) => JSX.Element
type Mappings = Record<string, GeneratorFunc>

type TurboFrameRenderDetail = {
Copy link
Member Author

@dem4ron dem4ron Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find @types for turbo, so inferred this from its usage.

import { ExercismTippy } from '../components/misc/ExercismTippy'
import { ReactQueryCacheProvider } from 'react-query'

type ErrorBoundaryType = React.ComponentType<any>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once updated to React 18, hopefully I can get rid of the any here

}

export function renderComponents(
parentElement: HTMLElement = document.body,
Copy link
Member Author

@dem4ron dem4ron Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed all

 if (!parentElement) {
    parentElement = document.body
  }

to default parameters

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that changing the behavior though? Previously, these calls would end up with parentElement being set to document.body, but they won't anymore:

renderComponents(null, ...)
renderComponents(undefined, ...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dem4ron Did you see this comment?

Copy link
Member Author

@dem4ron dem4ron Aug 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our case this doesn't change the behaviour, because we don't pass in falsy values.
I made sure things wouldn't fail, by passing in a default parameter a level above too, which used to be simply undefined

- const renderThings = (parentElement) => {
+ const renderThings = (parentElement = document.body) => {
    renderComponents(parentElement, mappings)
    renderTooltips(parentElement, mappings)
  }

  // This adds rendering for all future turbo clicks
  document.addEventListener('turbo:load', () => {
    renderThings()
  })
  ...

Another place where renderComponents is used is in LazyTippy.tsx:

export const LazyTippy = (props: LazyTippyProps): JSX.Element => {
  const { renderReactComponents, ...tippyProps } = props
  const [mounted, setMounted] = React.useState(false)

  const lazyPlugin = {
    fn: () => ({
      onMount: () => setMounted(true),
      onHidden: () => setMounted(false),
      onAfterUpdate: (instance: Instance) => {
        if (renderReactComponents) {
          renderComponents(instance.popper, mappings)
        }
      },
    }),
  }

In the worst-case scenario here, the value would be undefined too

On the other hand - given how mission critical this code is - I'm leaning towards reverting these changes to the input validation approach, because that is significantly more robust.

Copy link
Member Author

@dem4ron dem4ron Aug 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But here:

  const renderThings = (parentElement = document.body) => {

    renderComponents(parentElement, mappings)
    renderTooltips(parentElement, mappings)
  }

I'd just pass in document.body:

 const renderThings = () => {

  renderComponents(document.body, mappings)
  renderTooltips(document.body, mappings)
}
export function initReact(mappings: Mappings): void {
const renderThings = () => {

  renderComponents(document.body, mappings)
  renderTooltips(document.body, mappings)
}

// This adds rendering for all future turbo clicks
document.addEventListener('turbo:load', () => {
  renderThings()
})

// This renders if turbo has already finished at the
// point at which this calls. See packs/core.tsx
if (window.turboLoaded) {
  renderThings()
}
}

Co-authored-by: Aron Demeter <66035744+dem4ron@users.noreply.github.com>
app/javascript/utils/react-bootloader.tsx Outdated Show resolved Hide resolved
}

export function renderComponents(
parentElement: HTMLElement = document.body,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that changing the behavior though? Previously, these calls would end up with parentElement being set to document.body, but they won't anymore:

renderComponents(null, ...)
renderComponents(undefined, ...)

app/javascript/utils/react-bootloader.tsx Show resolved Hide resolved
}
}

let ErrorBoundary: ErrorBoundaryType = () => <></>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dem4ron dem4ron merged commit 793f01e into main Aug 29, 2023
@dem4ron dem4ron deleted the typescriptize-react-bootloader branch August 29, 2023 10:02
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

Successfully merging this pull request may close these issues.

3 participants