Make library chunks share the same react context with webpack - reactjs

I have two projects, one is a react app, the other is a library used by that react app.
The library is bundled with webpack and output three chunks, one called contexts, one called hooks, and one called components
In contexts can be found "myContext" react context (myLib/src/contexts/myContext.js)
In hooks can be found "useMyContext" react hook (myLib/src/hooks/useMyContext.js)
And in components can be found "MyProvider" component (myLib/src/components/MyProvider.jsx)
MyProvider component renders the myContext.Provider, giving it as value the output of the useMyContext.
Now in my react app, I render a component "MyComponent" wrapped with the "MyProvider" imported from my library.
"MyComponent" consumes the myContext imported from my library, and renders its value.
I would expect "MyComponent" to renders the value returned by the "useMyContext" hook, as it is implemented into "MyProvider"
However, "MyComponent" renders the initial value defined in myContext.
Note that if I export myContext and MyProvider in a same chunk, then it's working as expected, but I can't go for that solution in my real use case.
This makes me think that the myContext exported from the contexts chunk is not the same as the myContext used into MyProvider.
I made React a peerDependency in my package.json, to be sure the library relies on the React version from the app, and I made it an externals in Webpack config for the same reason.
What I tried so far was to use the Webpack feature "dependOn", in order to share react across the several chunks. But when I try that, the app raises error "__webpack_require__ is not a function" at runtime.
You can find the repository here https://github.com/c0ll1d3r/ReactContextLibraryIssue
Am I on the right track using dependOn ? If yes, would you know why I got that error ? Else what would be correct solution ?
Thank you for your help !

I found a way to make it works, actually as I mentioned, exporting everything from the same chunk was working (but not affordable in my project).
From that observation I made the conclusion the issue was that somehow two instances of "myContext" were created when imported from (or used in) two different chunks.
MyProvider was importing the context like this : import myContext from '../../myContext
And my app was importing the context like this : import { myContext } from 'myLib/contexts'
So I isolated my context in its own library, in order to be able to import it in a consistent way no matter what
Now, MyProvider and my app are importing this context the same way : import { myContext } from 'mycontextlib'
Making sure they are working on the same instance, and the value is well propagated

Related

Read and write to Redux store from multiple pages of a Gatsby App

My app is like this:
Gatsby project
index.tsx - Entry point of app - I call
<Provider store={store}>
<HomePage />
</Provider>
HomePage.js uses mapStateToProps and uses connect(mapStateToProps)(HomePage)
Inside my HomePage, of course I use multiple React components. And I can make use of the store, read/write of anything that happens on that HomePage.
On my Homepage, I have links to ActivityPage. Once I click on this page and land there - problem starts.
ActivityPage makes use of multiple sub-components to achieve tasks. How do I access read/write from ActivityPage?
I am unable to useDispatch to write or even read from the store. I can see the store on my ReactDev tools, but on trying to read/write I get 11 errors, mostly along the lines of I need to wrap Components with <Provider>
I read different solutions on Stack Overflow which suggested that I need to have mapStateToProps and use connect on whichever Component I need access the store.
This is not working, as I am specifically looking to get access to the store from a 'new page'. Do I need to go and make another store for my child pages? Help please.
:
Your component must be inside of a redux Provider component in order to use the redux hooks useSelector and useDispatch or the connect HOC.
In a typical React app you would place the Provider in your App component which is a parent of everything. However Gatsby uses a different setup where each page is totally independent, so there is no shared parent where we can place the Provider.
What Gatsby does have is an API for overriding customizing behaviors by defining functions in configuration files. There are a bunch of files that you can place in the root of your app, in the same folder as package.json and outside of src. The two that we will use here are gatsby-browser.js, which controls the client-side, and gatsby-ssr.js, which controls the creation of static HTML pages through server-side rendering. Both of these files support a function called wrapRootElement.
We use the wrapRootElement function to place our Redux provider as a wrapper around every page. Since we are using the same function in two files, the official "using-redux" example defines that function in a separate file and imports it into both of the configurations. wrap-with-provider.js is not a special file name, it's just a holder for the function.
A wrapRootElement function receives the element as a prop which is similar to the children prop. Our function creates a store instance and returns a Provider with that store which has the element as its child. We are creating the store in this function, so if your current redux file is exporting a created store as a constant, you'll want to export a callable function that creates the store instead. You can see their example here.
wrap-with-provider.js
import React from "react"
import { Provider } from "react-redux"
import createStore from "./src/state/createStore"
// eslint-disable-next-line react/display-name,react/prop-types
export default ({ element }) => {
// Instantiating store in `wrapRootElement` handler ensures:
// - there is fresh store for each SSR page
// - it will be called only once in browser, when React mounts
const store = createStore()
return <Provider store={store}>{element}</Provider>
}
Again, this file does not manipulate Gatsby behavior on its own. We need to export its value and assign it to a special variable in a configuration file to do that. Here are those files:
gatsby-browser.js
import wrapWithProvider from "./wrap-with-provider"
export const wrapRootElement = wrapWithProvider
gatsby-ssr.js
import wrapWithProvider from "./wrap-with-provider"
export const wrapRootElement = wrapWithProvider
The content is the same in both. All we are doing is importing the function that we created in wrap-with-provider.js and assigning it to the special named variable wrapRootElement which controls behavior.
The answer posted already and the Freecodecamp link posted in the comment were both really helpful resources, however for my specific use case I found looking at the Gatsby repository on redux the most helpful": https://github.com/gatsbyjs/gatsby/tree/master/examples/using-redux
Also, I ended up realizing that sessionStorage was more appropriate for my use case as I was trying to store data beyond a page refresh and I wasn't using the other features Redux offered. You can read more about localStorage here: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
and the differences between Redux and local storage here: Why redux instead of session storage
Also if you're using Redux I highly reccomend the redux-devtools extension for debugging you can install the package here: https://www.npmjs.com/package/#redux-devtools/extension and the browser extension here:
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en

React context from library not updating in consumer

I originally followed this project to add Firebase to a Gatsby React app. It involves making a Firebase context, wrapping a root layout with a provider, and then using a withFirebase HOC to wrap components with a Firebase consumer as needed. When I originally did it, it worked fine, but I wanted to move the code into a package I could reuse among my apps. Here's the HOC
export const withFirebase = (Component) => (props) => (
<FirebaseContext.Consumer>
{(firebase) => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
And each page starts with a Layout component that renders this:
<FirebaseContext.Provider value={this.state.firebase}>
<AppWithAuthentication>
{this.props.children}
</AppWithAuthentication>
</FirebaseContext.Provider>
AppWithAuthentication itself uses the withFirebase HOC as it needs Firebase to get the AuthUser (which is then stored in a context and passed down through a provider), and it's able to do so just fine.
All the above happens in the package code itself, but when I imported my package into my other React project, trying to use withFirebase stops working as any components wrapped with it never receive the updated context. I confirm this by checking the Component tree in React Dev tools, the Firebase Provider gets the updated not-null value, and the consumer inside AppWithAuthentication gets it too. But the consumers inside my actual app don't update (and I have this same problem with the AuthUser context I made in the same library).
I even thought that perhaps somehow the parent was rendering with the updated consumer but the children weren't re-rendering, but after counting the renders and logging them it was clear the components from my app were rendering more times than AppWithAuthentication. To make it a bit clearer, here's my component tree (starting from the Layout component at page root):
Here's Provider showing a value:
Here's AppWithAuthentication's consumer showing a value:
And here's the consumer from inside my application that doesn't have a value:
I'm completely stuck here and would appreciate any insight.
EDIT:
After more testing I found some more information but I'm still stuck. It would seem that when reloading my page, the Layout component renders 2 times, the Header and AppWithAuthentication components each renders 4 times, and the Login component renders only 1 time. Is this why the consumers aren't updating? (But then why does the Header component not get any updates when its updating as much as AppWithAuthentication?)
EDIT 2:
After more research, I think this issue has something to do with webpack? I'm using Neutrino.js to make my component library, and I take the output of its build as the library. I found this question that seemed similar and tried implementing the fix like so in my .neutrinorc.js:
const reactComponents = require('#neutrinojs/react-components');
module.exports = {
use: [reactComponents(),
(neutrino) => {
neutrino.config.output.library("upe-react-components");
neutrino.config.output.libraryTarget("umd");
neutrino.config.mode("development");
}],
};
But it didn't fix the issue. Has anyone encountered issues with webpack breaking React context?
I heard from a clever friend of mine that the problem is because React contexts are defined at the module-level, and so since you tried to have your contexts in separate entrypoints, things will not go so well.
I think you can try making an index.js file that re-imports and exports everything, and then things should work as it's all consolidated in one module.

Every react.js package works normally with next.js after the javascript enters the application?

I'm new to using Next.js to create websites with react.js. I am trying to put the module "react-insta-stories" (https://github.com/mohitk05/react-insta-stories). Only he accuses that the document is not defined. I know you've had similar questions here, but I wanted to ask you something different, more general ... Because there are several awesome modules that I wanted to use in this project and the next.js seems to make it difficult. Some modules just that only speak with the "browser" . The question is ... can I use any module even from react.js after javascript instantiates my application with no problems with next.js? And does anyone know how I could put this react-insta-stories in my project? Because I tried some solutions, but I think they are beyond my knowledge.
By default next.js performs server side rendering. The quick and dirty way of getting react components that require the document or window to work is to call them as a dynamic component in next.
If you can a better way that allows for server side rendering of your component is to run this code in a useEffect hook or similar, as this will be sent to the browser to run.
For example
import React, { useEffect, useRef } from 'react'
function ComponentWithEffect(): JSX.Element {
const divRef = useRef<HTMLDivElement>(null)
useEffect(() => {
divRef.current?.width = window.width
divRef.current?.height = window.height
}, [])
return(
<div ref={divRef}/>
)
}
But this is not always possible, because with external libraries you might not have access to the underlying methods. There are heaps of examples in the repo as well.

Is it possible to render React.JS component where the source of the component pulled from a rest api call?

I am wondering if it is possible to render a react component if the source of the component is pulled from a rest api call. For example if the component exists within a database.
Please do not simply answer with "I would not do this" or "it is not recommended". I understand this is not the normal approach.
The use case is having a list of "widgets" that reside in the data store and are pulled in as needed. As well as building up components on the fly.
Ideally this would happen at run-time on the client, but I would also be ok with switching to purely server-side react if that is the only way.
Note: This is different than code-splitting. I have tried various way of invoking "import(...)" but since the component is coming from a rest api call I was not able to get this to work. If there is a way, please let me know.
I have seen an example that was similar located here: https://codepen.io/qborreda/pen/JZyEaj?editors=1010. But it had a few restrictions. Namely any additional imports that the component itself would need.
function loadRemoteComponent(url){
return fetch(url)
.then(res=>res.text())
.then(source=>{
var exports = {}
function require(name){
if(name == 'react') return React
else throw `You can't use modules other than "react" in remote component.`
}
const transformedSource = Babel.transform(source, {
presets: ['react', 'es2015', 'stage-2']
}).code
eval(transformedSource)
return exports.__esModule ? exports.default : exports
})
}
loadRemoteComponent('https://codepen.io/tonytonyjan/pen/QEawag.js').then((Hello) => {
ReactDOM.render(<Hello/>, document.getElementById('hello'))
})
I had thought that I could store the transpiled version of the component and pull it into react. But so far I have not been able to get this to work. Generally the issue is that either the component does not render or React throws errors because it is expecting a component not raw text.
I also tried manually transpiling on the client similar to the "getting started" examples on the react web site by including the babel standalone processor and react scripts on the client (via script tags).
So I thought I would ask the community. To recap, my question is: Can I store a react component in some datastore, pull it into my react web site and render it?
Can this be done purely on the client?
Would using React Server Side Rendering make this easier?
Or can this only be done through a build step prior to launching the site?
Any help is appreciated. Either detailing the steps required, or a simple working example would be great. And I actually do not mind following links to get my answer. (I know, shocking!)
Oh, in case you are wondering I have been using create-react-app to generate the app, but I have also tried creating the app manually setting up webpack/babel myself.
Thank you.

IoC in React without typescript

I'm pretty new to React, and I would essentially have a service (one single instance) that I could somehow inject and use in multiple React components.
So far, the options I've come across by googling were:
Using React's context, but it's not recommended, because it is an undocumented API.
Passing the object along in the props, from component to component, but it feels a bit inelegant and tedious
Using an IoC container, such as inversifyJS, who looks great, only it relies on typescript, and I don't wish to write my React app in typescript.
Now, Inversify can apparently be used in vanilla JS, with inversify-vanillajs-helpers, but when I tried using it in React to resolve a component (my App component), it kept throwing an exception stating
"Missing required #injectable annotation in: ReactComponent"
So, I'm trying to figure out a way to get an instance of my service (the same instance shared across the few components that use it), either by making inversify work with React but without typescript, or a new approach I haven't explored yet.
Any ideas ?
There are a few things that you can do if you don't like TypeScript:
First, you are going to need the inversify.decorate method.
Second, this method is probably going to be too verbose for you. We created a helper called inversify-vanillajs-helpers for that reason.
And third, React doesn't play nicely with constructor injection so we created a helper to facilitate lazy property injection called inversify-inject-decorators.
Those three tools should lead yo to the desired result :)

Resources