facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
227.85k stars 46.51k forks source link

Is it possible to share contexts between renderers? #17275

Open diegomura opened 4 years ago

diegomura commented 4 years ago

What is the current behavior?

Hey 👋 I maintain react-pdf. Thanks for your awesome work and making react-reconciler for us to use!

I've got many issues lately regarding context not working on my library and when doing tests I found out that context values aren't shared between renderers. This makes it impossible to share state such as themes, i18n, redux and more. As a bit of context, React-pdf is not a primary renderer, and as such, when used in the browser it runs on top of react-dom.

I found the isPrimaryRenderer reconciler option that's supposed to be used for "multiple renderers concurrently render using the same context objects" but still any access of the context inside react-pdf components get's just the initial value (even if the context was updated with other value). The same happens for react-art that also set isPrimaryRenderer=false.

Minimal demo

I prepared a quick demo using react-art so you can see how it currently works:

https://codesandbox.io/s/pedantic-hill-54kid?fontsize=14

What is the expected behavior?

Share contexts between renderers when using isPrimaryRenderer config. Is there a way of achieving this? Am I missing something?

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React: 16.11.0 React-dom: 16.11.0

sophiebits commented 4 years ago

Check out how react-art’s Surface component works. In particular, pass a class component’s “this” value to updateContainer:

https://github.com/facebook/react/blob/master/packages/react-art/src/ReactART.js

Hope that helps.

markerikson commented 4 years ago

Paging @drcmda and @lavrton , who might be interested in bridging contexts between renderers.

drcmda commented 4 years ago

@sophiebits is this possible with function components as well?

lavrton commented 4 years ago

@markerikson thanks for mentioning.

@sompylasar, unfortunately, it doesn't help. The minimal demo, posted by @diegomura, shows the issue with react-art.

Related issue: https://github.com/facebook/react/issues/13332

As far as I know, there are currently no ways to pass contexts automatically into other renderers. The workaround is to "bridge" the context manually:

<ThemeContext.Consumer>
      {value => (
        <Stage width={window.innerWidth} height={window.innerHeight}>
          <ThemeContext.Provider value={value}>
            <Layer>
              <ThemedRect />
            </Layer>
          </ThemeContext.Provider>
        </Stage>
      )}
</ThemeContext.Consumer>
diegomura commented 4 years ago

@lavrton I came up to the same conclusion, although in my quick test just adding the context provider inside my renderer elements was enough to bridge the state (without manually passing value from one consumer to the provider). But I might have to check that again.

@sophiebits thank you so much for you quick response, but as @lavrton said, this issue is also happening in react-art. Does that means this is a bug?

RodrigoHamuy commented 4 years ago

Hi @lavrton , do you know why is not possible to pass context between renderers automatically? Sorry, it is still not clear to me why this doesn't work.

Coming from the same issue but with react-pixi:

Demo: https://codesandbox.io/s/react-pixi-context-example-eky26 Issue: https://github.com/inlet/react-pixi/issues/165

Thanks

drcmda commented 4 years ago

It only looks like the jsx you're giving to pixi via the stage component is in the same tree as the dom related jsx, which belongs to the react-dom reconciler, the one that holds the context provider. stage constructs a new reconciler, which is entirely isolated, and renders the children it received (the jsx inside stage). pixies reconciler has no idea where it is, it bears no relation to the surrounding reconciler and can't share its dynamics, like context, suspense, error boundaries and so on.

if react wanted to solve this we would probably need something like a first class react component, like a portal, which is returned by the react-reconcilers createContainer function. this will be rendered into the root reconciler as a regular element belonging to its tree. this way it would know it must pass its internals on. @gaearon described something like that once, but can't find it right now.

jquense commented 4 years ago

This doesn't seem to be solved? I'd also really like to see this happen!

stale[bot] commented 4 years ago

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

drcmda commented 4 years ago

It's very much still affecting us

stale[bot] commented 3 years ago

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

lavrton commented 3 years ago

Sorry, bot. But many of us are still interested in the solution.

hansagames commented 2 years ago

HI, anyone have found good way to fix this ? tried out solutions suggested here but no luck (got errors from react it self after doing that)

Minious commented 8 months ago

Still having the issue trying to use next-intl NextIntlClientProvider inside the react-pdf.

Error: Failed to call 'useTranslations' because the context from 'NextIntlClientProvider' was not found.

lavrton commented 8 months ago

BTW, with https://github.com/pmndrs/its-fine, the issue can be resolved. All custom renderers may use it to automatically bridge all the contexts. react-konva and react-three-fiber are already doing that.

drcmda commented 6 months ago

https://twitter.com/sebmarkbage/status/1765881794648269239 😂

but maybe this project was silly enough for react to see that this is needed. why have secondary renderers when they can't participate in primary matters.

rickhanlonii commented 6 months ago

Sharing the context from @sebmarkbage on https://github.com/facebook/react/pull/28524:

Spent some time investigating. Regardless the versions would have to be in perfect lock-step but that turns out to be a requirement anyway so that's fine.

The proper solution that behaves like a Portal should forward the value during a concurrent render into the child. Meaning it should actually be part of the same render phase. That's not just an issue with Context but Props too. Ideally it shouldn't have to commit the outer root before forwarding the inner values.

Additionally, it's not just Context that is contextual. There's internal contexts and for example if you Suspend in the child renderer outside a Suspense boundary, it should affect the parent render. E.g. a startTransition in the parent should stall or .

That's the proper implementation. It's very difficult, and may require compromising on performance for everyone, but it's not impossible. Realistically, that amount of work, I don't see that getting prioritized any time soon. There's just way more pressing issues.

The smaller version of converting an outer render into a sync secondary render after commit, with no Suspense or Transition integration etc. is easier. That might be "good enough". However, there's the question of whether encouraging thinking of these is a single root given that new features won't work seamlessly anyway. So it might've been "good enough" before but not "good enough" in the future.

There's also a whole other approach of creating fake DOM nodes using the DOM renderer.

mathematikoi commented 4 months ago

I managed to achieve this by writing a shim for react context to use events, you can check it out here:

https://github.com/mathematikoi/eventually.git

still wip, but you can see for yourself

https://codesandbox.io/p/sandbox/context-events-qnqr4f?file=%2Fsrc%2F100%25-event-driven.tsx