react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

Export context objects that depend on accessing parent context to work when nested

Open JacekKiz opened this issue 1 year ago • 3 comments

🙋 Feature Request

Export context objects from @react-aria that depend on accessing parent context to function properly. This would allow for forwarding the context to micro frontend applications where each application is isolated in a separate React tree.

🤔 Expected Behavior

It would be great if FocusContext in @react-aria/focus/src/FocusScope and Context in @react-aria/overlays/src/useModal were exported.

😯 Current Behavior

The context objects are not exported.

💁 Possible Solution

Export the context objects. Something like: #3310

🔦 Context

I work on a project which is using webpack module federation to enable micro frontend architecture. Each micro frontend application is isolated in a separate React tree. The isolation has some benefits and was done for a reason but it also comes with a downside of the context values provided in the parent being inaccessible in a micro frontend app, since it's in a different React tree. When you nest components that use ModalProvider or FocusScope, they require accessing data from parent context to function properly. An example of this would be a Dialog component which contains a micro frontend application inside it and that micro application contains a Picker. If the picker dropdown does not have the connection to Dialog's Focus/Modal context, things don't work as expected (for example, selecting an option within the picker dropdown counts as clicking outside the dialog and the dialog does not get aria-hidden attribute when picker's dropdown is open). In order to get components that use @react-aria's FocusScope and ModalProvider to work with this setup, we would need to use the context and render the provider with the same value in the React root of the micro frontend app.

Is exporting these context objects something you would be open to?

JacekKiz avatar Jul 14 '22 17:07 JacekKiz

We'd prefer not to expose internal contexts. These are implementation details and could change at any time. If we exposed them, this would no longer be possible as they'd become public API.

Without much information, it sounds to me like perhaps your micro frontend architecture is too granular? If you split it such that all components within a modal live within the same react root, you might have an easier time.

In addition, React itself strongly discourages having multiple roots on the page at once. You might want to consider an approach where you expose React components from your split points, and render these within a host component rather than rendering directly into a DOM element as a separate root. This way, everything would be in a single React root, but still split into multiple builds.

devongovett avatar Jul 16 '22 00:07 devongovett

We are considering rendering in the same react tree because of these issues and potential concurrency issues in react 18, but at least theoretically it seems to make sense in our setup to isolate our micro frontends as much as possible. In our setup there is a shell application which loads many micro frontends. The most important consideration in rendering those micro frontends in the same react tree is the implicit coupling of the shell application and micro frontends, by having all context providers of the shell application accessible from the micro frontends. It's true that there is usually some higher level public API on top of each context, and there is no need for directly using context objects. But there are edge cases like this and there are some ways of accommodating for such use cases, without compromising on the public API.

  • Undocumented export with UNSAFE_ prefix. That's what react-router does.
  • Undocumented export, excluded from typings, while one can @ts-expect-error the import and use it on their own risk. Kind of similar to how getCollectionNode is excluded from the public API, but nothing prevents one from accessing it on their own risk.
  • Exporting a utility, specific to this use case. Recoil.js provides useRecoilBridgeAcrossReactRoots, as an example. But I'm guessing you wouldn't be a fan of it :D

Is any of these approaches something you would consider @devongovett ?

alirezamirian avatar Jul 18 '22 15:07 alirezamirian