liferay / liferay-frontend-projects

A monorepo containing assorted Frontend Infrastructure Team projects
Other
66 stars 67 forks source link

How to apply the useContext hook with Liferay widgets containing React components. #1193

Closed christopher1986 closed 3 months ago

christopher1986 commented 4 months ago

I am currently following the Creating a Basic Custom Element tutorial and I am not sure how the useContext hook can be used across multiple widgets.

In one of my single page application (SPA) built completely in React I have a custom snackbar which relies on SnackBarContext.Provider and the useContext hook. Each component in this React project will receive the same context from the SnackBarContext.Provider.

Question: I would like to implement something similar with (custom element) widgets in Liferay. How would one implement this with custom elements in such a way that all the Liferay widgets on a single page share the same context?

izaera commented 4 months ago

What you need is to have a single copy of the context (in fact of anything that is shared between widgets in your app) in the JavaScript interpreter so that all things running in the browser see the same copy.

To achieve that, you need to put shared things into browser modules so that you can import them from the same URL. That way you make sure your browser uses the same copy no matter where you import things from.

And a way to do that (there are more, probably) is to use import maps, for example, to register "global shared libraries". Have a look at this sample -> https://github.com/liferay/liferay-portal/tree/master/workspaces/liferay-sample-workspace/client-extensions/liferay-sample-etc-frontend-3 . It demonstrates how to share JS code by means of an import map.

christopher1986 commented 4 months ago

@izaera thanks for your response. I will look into the examples you provided. I thought the only way to share non-static information (e.g. objects, arrays, etc..) with other Liferay widgets would be to add them to the global Window object or use the window.Liferay.on method to dispatch global events between widgets.

From your answer I understand I can share functionality (global shared libraries) between widgets. But how do I update the state (e.g. object) and make that change visible in one or more other widgets?

izaera commented 4 months ago

From your answer I understand I can share functionality (global shared libraries) between widgets.

Yes. And my idea is that if you can share global memory you can share react state too (some way).

But how do I update the state (e.g. object) and make that change visible in one or more other widgets?

TBH I'm not sure. My guess is that you would need to make both custom elements be rendered by React and make React aware that those two components are inside its hierarchy.

So, first of all you would need to share the same copy of React for the two (shared library you provide or use the react that DXP provides through an import map -> https://github.com/liferay/liferay-portal/tree/master/workspaces/liferay-sample-workspace/client-extensions/liferay-sample-custom-element-4)

Then you would need to wire them properly inside React''s hierarchy.

The second part is a generic problem, it's not exclusive of DXP and would happen in any setup that uses custom elements rendered by React so there must be a generic solution or a statement from React saying that it's not supported, but I have never investigated it fully.

Maybe @bryceosterhaus may provide more insight? He's knows more React than me :sweat_smile:

bryceosterhaus commented 4 months ago

In order to share state across components using context, the components would need to be in the same react root with a context provider near the top of that root and then your components rendered underneath.

In your case, it seems like you want to use multiple custom-elements that use react components. In that case, each custom-element would be rendering a new react root and so it wouldn't be possible to just use context to share state.

In order to solve this, you may be able to utilize an existing library that stores the state on the window or local storage or you can roll your own solution to solve this.

Here is a rough sketch of how I would imagine it: Screenshot 2024-02-19 at 9 49 08 AM

Notice that the state is hoisted to be global and then you would need a custom implementation in a context provider that can trigger state changes across both roots.

Hope this is helpful!

izaera commented 4 months ago

each custom-element would be rendering a new react root

True. I didn't realize this was the case :+1:

christopher1986 commented 3 months ago

@bryceosterhaus and @izaera sorry for the late response. I have been pretty busy at work and totally forgot about this thread. @bryceosterhaus your detailed answer helped me alot. It also aligns with my ideas on how this problem can be solved.

I am satisifed with your solution. Should I therefore close this issue or does it need to stay open?

izaera commented 3 months ago

Glad to see you found a solution.

We can close the ticket :+1: