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

Custom Renderer in React 18 #950

Closed
nathnhughes opened this issue Apr 21, 2022 · 7 comments · Fixed by #969
Closed

Custom Renderer in React 18 #950

nathnhughes opened this issue Apr 21, 2022 · 7 comments · Fixed by #969

Comments

@nathnhughes
Copy link

Description

Adding a custom renderer in React 18 seems to throw a couple of errors, namely:

  1. The types of createElement are no longer compatible
  2. ReactDOM.render is no longer supported in React 18 and forces the app to behave as if it's running React 17 until the method is removed

Reproduction

https://codesandbox.io/s/morning-fire-tqxpqs?file=/src/App.tsx

Steps

  1. Go to https://codesandbox.io/s/morning-fire-tqxpqs?file=/src/App.tsx
  2. Click in the search box
  3. Type anything
  4. See console for error

Additionally, using createElement throws the following type error:

Screenshot 2022-04-21 at 13 02 28

However this doesn't seem to appear in the reproduction (probably due to the package versions, see Notes section).

Expected behavior

The component should work without createRoot error and should not prevent React 18 from being used.

Environment

Notes

  1. In the codesandbox, the latest version of React available is: 18.0.0-alpha-fd5e01c2e-20210913 and the latest version of React-DOM is: 18.0.0-alpha-fd5e01c2e-20210913. This may result in slightly different behavior to my local environment which is running v18.0.0 for both packages.
  2. The component does still functionally work in practice, however, it's throwing the console error and preventing React from using version 18 features that's the issue.
  3. As a resolution for this, it would be great if someone was able to provide an example of how to structure a custom renderer in React, perhaps using createRoot as the console error suggests.

Example code

For context, an example snippet of my production code that is experiencing the errors can be seen below:

import React, { createElement, Fragment, useEffect } from 'react'
import { render } from 'react-dom'

// Styles
import * as S from './styles'
import '@algolia/autocomplete-theme-classic'

// Components
import Item from './Item'
import NoResults from './NoResults'
import Footer from './Footer'

// Algolia
import { autocomplete, getAlgoliaResults, AutocompleteOptions } from '@algolia/autocomplete-js'
import algoliasearch from 'algoliasearch/lite'

const searchClient = algoliasearch('XXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')

export type SearchResult = {
  objectID: string | number
  title: string
  description: string
  snippet: string
  slug?: string
  breadcrumbs?: string[]
}

const Search: React.FC<Partial<AutocompleteOptions<SearchResult>>> = props => {
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const search = autocomplete<SearchResult>({
      container: '#algolia-search',
      renderer: { createElement, Fragment },
      render({ sections }, root) {
        render(
          <div className="aa-PanelLayout aa-Panel--scrollable">
              {sections}
              <Footer />
          </div>,
          root
        )
      },
    /* ... getSources, etc ... */
    })

    return () => {
      search.destroy()
    }
  }, [props])

  return <S.Container ref={containerRef} id="algolia-search" />
}

export default Search
@Haroenv
Copy link
Contributor

Haroenv commented Apr 21, 2022

I haven't investigated much in React 18 yet, but I think this is the equivalent of render: https://codesandbox.io/s/quizzical-pare-nnunmt?file=/src/App.tsx

Although I feel like you may need to make sure you don't create the root multiple times. Maybe the right approach is via a Portal?

@nathnhughes
Copy link
Author

@Haroenv cheers for looking into this. Looks like, unfortunately, your potential fix is rendering the items in body rather than the search component:

Screenshot 2022-04-21 at 18 02 38

In any case, i'll give your example a try locally and see if I have any luck.

Re: creating a root multiple times, I think you're right there too.

When trying this locally before I was getting errors about the root already being instantiated and to call root.render to update rather than re-instantiating...

Perhaps the root needs to be defined outside of the effect or be removed in the effect clean-up?

@Haroenv
Copy link
Contributor

Haroenv commented Apr 22, 2022

We'll make sure to update these guides soon, but for now you can either

  • use autocomplete-core where you do the rendering completely yourself, and thus won't be mixing react paradigms
  • ignore the warning about ReactDOM.render
  • don't pass render and createElement ( use html`..., and consider it as a contained library)

Hope that helps!

@savager
Copy link

savager commented Apr 25, 2022

  • use autocomplete-core where you do the rendering completely yourself, and thus won't be mixing react paradigms

Could you perhaps elaborate on this a touch? Or the third option. Perhaps an example. I don't want my entire application to run in react 17 mode, I'm not super versed in algolia's component structure. Thanks

@Haroenv
Copy link
Contributor

Haroenv commented Apr 25, 2022

Only the autocomplete part would run in React 17 mode, as it's a separate root, but I see indeed how you'd want to avoid the issue.

For option 1, autocomplete-core, here's an example: https://codesandbox.io/s/frosty-hooks-ptzbc5 which is slightly modified from the examples in this repository (react-renderer) to use React 18 and runs without warnings

For option 3, you can follow the guide on https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/templates/#returning-html combined with a regular div in the react component that becomes controlled by autocomplete

@savager
Copy link

savager commented Apr 25, 2022

I see, I understand now then, I'm ok if the autocomplete uses react 17 for now, I wasn't aware the scope was limited. Thanks a lot!

Thanks for the other explanations as well. Helped a lot.

@sarahdayan
Copy link
Member

sarahdayan commented Apr 27, 2022

@nathnhughes @savager You shouldn't create the root on the same container that you pass to the container option, because this container is only for the search box. If you do it, the search box will disappear as soon as you type something, and be replaced with results.

Autocomplete lets you pass a panelContainer for rendering results into. The thing, though, is that you shouldn't render directly in it, but inside an inner container (.aa-Panel) which you have access to as the root argument in Autocomplete's render option. This container takes care of positioning the panel properly.

Therefore, you can create the root in there once by storing it in a ref, then render in it: https://codesandbox.io/s/elated-danilo-0itu34?file=/src/App.tsx

Autocomplete will warn against using the render option instead of renderer.render but you can ignore it for now.

Regarding warnings about ReactDOM.render, you should no longer see them, but the only reason it happened in @Haroenv's sandbox was because React renders components twice in StrictMode in development (including useEffect with an empty dependency array). It's always safe to check if the warning disappears or not in production to determine whether there's an actual issue or not.

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 a pull request may close this issue.

4 participants