dennisreimann / uiengine

Workbench for UI-driven development
https://dennisreimann.github.io/uiengine/
MIT License
368 stars 25 forks source link

React Context not available in components #52

Closed MrAvantiC closed 5 years ago

MrAvantiC commented 5 years ago

Hey Dennis! Sorry for this neverending story, but I have another problem using the jsx/webpack adapter. :/

Describe the bug We have a use case where we need data available to every component. To avoid "prop drilling" we want to make use of the Context-API for this.

I tried to make use of the react-server-render and react-client-render for this to make the context available to every component/variant in UIEngine. This looked good at first, since the Context-Provider showed up in the component tree:

Bildschirmfoto 2019-08-10 um 15 42 59

Unfortunately, the actual context is not accessible in the components of the UIEngine. It appears like the context is not passed through to all the underlying components.

To Reproduce Steps to reproduce the behavior:

  1. A minimal example of the problem can be found here: https://github.com/MrAvantiC/uiengine-context
  2. Install dependencies and start development server
  3. See the console-ouput of the render() method in <Footer />

Expected behavior The context should be available in the <Footer /> component. I can confirm that the same code works in CRA/Next.js.

Kind regards, René

dennisreimann commented 5 years ago

Without having had a deeper look into this: Did you try and compare the React Sample Project? It does not utilize the Webpack adapter, but also uses the Context API https://github.com/dennisreimann/uiengine-sample-react/blob/master/lib/uiengine/react.js

MrAvantiC commented 5 years ago

Well, as far as I can tell, the Webpack adapter doesn't offer a similar hook like wrapElementBeforeRender, does it? I tried to imitate that functionality by utilizing the renderers like that:

// react-server-render.js

module.exports = function serverRender(Component, props) {
  return renderToString(
    <ThemeProvider>
      <Component {...props} />
    </ThemeProvider>
  )
}

Not sure if that's the best way but it did insert the ThemeProvider into the component tree atleast. Just accessing its provided context doesn't work.

dennisreimann commented 5 years ago

Yep, I have tried several ways now, but I also can't get the ThemeProvider to update when it is passed the value prop. I am not really familiar with the React Context API, but from what I saw in the docs it should be working :(

MrAvantiC commented 5 years ago

Damn!

but I also can't get the ThemeProvider to update when it is passed the value prop

Does that mean you were able to access the this.context in the Class Component but it (just) didn't update? Because in my tests this.context wasn't even defined (probably not passed through by the webpack build process?) in the Component.

dennisreimann commented 5 years ago

It is defined when you initialize it with React.createContext(‚initial value‘), but that does not help.

Had another look earlier today and my guess is that it boils down to the same problem as mentioned in #46, namely that somehow stuff specified in the render files is not accessible in the variants, because they are two separate files and not bundled together.

You can see this when you are using the latest versions (2.4.x) and have a look at how they are build (dist/_webpack) and integrated in the HTML.

Don‘t know for sure, but that‘s what came to my mind as the most likely thing to cause this.

MrAvantiC commented 5 years ago

namely that somehow stuff specified in the render files is not accessible in the variants

I don't quite understand this point. After all, the variants do require the actual components ...shouldn't that be resolved by webpack?


Unfortunately, my webpack knowledge is kinda limited. Do you see any possible solution to this - like packaging them together somehow?

dennisreimann commented 5 years ago

After all, the variants do require the actual components ...shouldn't that be resolved by webpack?

That's correct. Nevertheless, currently the variants and render functions (serverRender and clientRender) live in separate bundles.

See the source of this example for instance:

<script src="/_webpack/jsx-client.js"></script>
<script src="/_webpack/src_elements_label_variants_label_jsx-client.js"></script>
<script>
        window.UIengineWebpack_render(
          window.UIengineWebpack_component,
          {"id":"name","title":"Name"}
        )
</script>

This is the generated output:

They aren't bundled together and from what I have seen so far, this is the problem with the React hooks and context APIs. They seem to rely on side effects that require both files to share the same scope (and by that the exact same React instance).

I'll tinker more with that when I find the time to do so :)

MrAvantiC commented 5 years ago

No worries, we'll figure out a way that works for our use case. :) Appreciate your commitment!

dennisreimann commented 5 years ago

I got it kinda working – at least on the client-side. Same problem still when rendering server-side, which I don't understand yet as the React instances there are the same too.

However, as I am still unsatisfied with the approach I'm taking to make this work and I am still trying to find a better way, here is something I can imagine to work for you in the meantime: Use the Webpack externals option to extract React and ReactDOM from the bundle:

externals: {
  react: 'React',
  'react-dom' : 'ReactDOM',
}

You would then have to provide them via CDN or local linking in the preview uiengine.html template.

MrAvantiC commented 5 years ago

Hey Dennis, I tried excluding them from the build (which worked) and included them via CDN: https://github.com/MrAvantiC/uiengine-context/commit/29457cf429a07137c78b4d764f93ef48e95c8d77

Unfortunately, accessing this.context in the Component still returns undefined. Am I missing something?

dennisreimann commented 5 years ago

Ok, took another look and I really don't know enough about how this is supposed to work to fix it. I'd appreciate someone having a deeper look and assisting me with debugging that.

From what I saw the problem seems to be that the context cannot be shared across React instances coming from different bundles.

dennisreimann commented 5 years ago

For reference:

dennisreimann commented 5 years ago

Status update: I got it working now :)

Will clean up the code and test whether or not this helps with the hooks issue as well during the next days.

dennisreimann commented 5 years ago

See the example here: https://uiengine-sample-project.uix.space/testcases/examples/ReactContext/

dennisreimann commented 5 years ago

v2.5 is available now and fixes this :)