Module federation and React Context hooks - javascript

When using React useContext hooks, it currently requires an import from a component higher up in the tree to pass in the Context object. Say for example:
// index.jsx
export const MyContext = React.useContext('1')
export function MyApp(props){
return (
<MyContext.Provider value='1'>
<ChildComponent />
</MyContext.Provider>
)
}
// ChildComponent.jsx
import {MyContext} from './index';
export function ChildComponent(props){
const context = useContext(MyContext);
return (<p> {context} </p>)
}
But if the child component is federated, it will be in a separate repo on its own, and cannot import from './index'.
At the MyApp level, I can import the ChildComponent with:
const ChildComponent= React.lazy(() => import('federatedApp/ChildComponent'))
provided that within the Webpack config, the federatedApp is named as a remote, and the HTML doc includes the remote modules entry point .js. But how might I give MyContext to the remote federatedApp?
Guess answer: Would I need to separate the two components in index.jsx into two files and expose them both separately?

Related

Component inside a component

I am working with React.js
I want to use a component x inside app component(app.js) that is inside index.js.
It does not work.
**
Error
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of `App`.
**
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
App.js
import "./styles.css";
import {SubComponent} from "./components/Subcomponent";
export default function App() {
return (
<SubComponent/>
);
}
Subcomponent
const HelloWorld = ()=>{ return(<p>Hello World !</p>)}
export default HelloWorld();
Subcomponent folder is :
Your subcomponent:
needs to export the same name as what's being imported in App - either use the same named import/export in both places, or use default import/export in both places. (You're currently importing a named SubComponent but default exporting)
needs to export a component, which is a function, so that it can be called with React.createElement inside App. Don't invoke the function yourself.
App.js
import {SubComponent} from "./components/Subcomponent";
Subcomponent
export const SubComponent = () => {
return(<p>Hello World !</p>)
};

Correctly create global variables in React

As title. I've searched about this problem on this site but I didn't find a solution for my case.
I've defined a global variables in public/static/js/A.js in my Visual Studio project:
var pVariable=new Class1();
There is a function LoadList() in Class1.
That variable will be used in several functions in my project, so I set as a global variable, and I included the JS file to public/index.html in the same project:
<script type="text/javascript" src="%PUBLIC_URL%/static/js/A.js"></script>
I use that variable in src/Clsb.js in the same project....
var alist=pVariable.LoadList();
export default class Clsb extends Component{
render(){
return (<Table dataSource={alist} />);
}
}
When I start debug in Visual Studio , I got an error:Failed to compile: 'pVariable' is not defined no-undef
But I am sure the JS file contains that variable is included. Could someone guide me to fix where I made it wrong?
You can do that by storing the variable in window variable
first store the value of your variable in A.js like this
window.pie = 3.1416;
And you can see the variable value in your Clsb.js component like
console.log(window.pie);
As phuzi said in the comment to your question, declaring global variables in react is a bad idea, in this case the ideal would be to use redux, or else the context api (context hook) so you can access and make the variable available throughout your system.
Link to docs
https://reactjs.org/docs/context.html
👇 context with functional component
let's create a context like hook
/src/context.js
import React, { createContext, useState, useContext} from "react";
const UserContext = createContext();
export default function UserProvider({ children }) {
//your variables
//example
const [person, setPerson] = useState('John');
return (
<UserContext.Provider
value={{
person //variables to export
}}
>
{children}
</UserContext.Provider>
);
}
export function useUser() {
const context = useContext(UserContext);
if (!context) throw new Error("useUser must be used within a CountProvider");
const { person } = context;
return { person };
}
after creating the context, around your app you need to place the provider
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import UserProvider from "./src/context.js";
ReactDOM.render(
<UserProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</UserProvider>,
document.getElementById("root")
);
after you already have the provider you can access any file
src/page.js
import React from "react";
import { useUser } from "./context.js";
const Page = (props) => {
const { getPerson } = useUser(); //variable available by context
return (<h1>Test h1</h1>)
};
export default Page ;
obs: i didn't test the code
Global variables is not a good practice in React. Whenever you find you find yourself needing that, it's most likely that what you want us instead Global state Management.
My recommendation is :
-first to try React's built in global state Management toolsn like Context API https://reactjs.org/docs/context.html
-Then if the first doesn't serve your need, try third party state Management libraries like redux from https://react-redux.js.org/introduction/ or mobx

React Context API not working from custom NPM component library

I've built a ReactJS component library that I use for multiple projects installed via an NPM package using a sim link. I want to use the context API to pass data from a parent component served from the component library to my base project to be consumed by multiple consumer components also served from the component library. When I try the context is always undefined in my child components.
If I place my consumer component in my provider component within my library it works like a champ but this defeats what I'm trying to achieve. If I export both the provider and the consumer to my base project the consumer doesn't see the provider.
This is from my base project
import { Screen, COD, GenericSocketServer } from 'component-library'
export default class View extends React.PureComponent {
render() {
return (
<Screen className="screen odmb1">
<GenericSocketServer>
<COD />
</GenericSocketServer>
</Screen>
)
}
}
This is my provider code exported from my 'component-library'
import React from 'react';
import MyContext from "./context";
import COD from './../cod';
export default class GenericSocketServer extends React.Component {
render() {
return (
<MyContext.Provider value={{ foo: 'bar' }}>
<COD />
{this.props.children}
</MyContext.Provider>
);
}
}
This is my content code used in 'component-library'
import React from 'react'
const MyContext = React.createContext()
export default MyContext
This is my consumer component exported from 'component-library'
import MyContext from "../GenericSocketServer/context"
class COD extends React.Component {
render() {
return (
<React.Fragment>
<MyContext.Consumer>
{(context) => {
/*
context comes back undefined
I expect { foo: 'bar' }
*/
console.log('context :', context)
return (
<p>This should work</p>
)}}
</MyContext.Consumer>
</React.Fragment>
)
}
}
Context always comes back undefined as if it doesn't see the parent provider. I think I'm ether doing something wrong initializing the context myself or for some reason the two components I'm importing just don't share the same context. Please help!! Not sure if I should give up on this and just use redux.
Maybe you are making multiple instances of the component providing the context. Let's say you have a component Sound, which starts by:
const { Provider, Consumer } = React.createContext();
If you import this library from your main project, the context will be created at the global space. You then use it to render your document tree. But in another component you also imported this library, which had to be resolved during webpack transpilation. It thus has its own copy of the above lines and a context object created in its own space. The problem occurs when you try to use the Consumer, because the Provider was only made by the main project for the first context object, and the second context's provider instance was never instantiated, thus returns undefined.
A solution to the problem is to enforce a single context object, which you can achieve by telling the second component's webpack that the provider-owning library is an external, so when webpack reaches e.g. the "import sound" line, it will not go further and will assume this dependency is resolved at runtime. When runtime comes, it will take it from the same place where the main project is taking it. To do this in webpack, e.g. for above "sound" library, add this to your other component (not main project):
{
...
externals: {
...
'sound': 'sound'
}
...
}
Also in your component package.json:
{
...
peerDependencies: {
"sound": "^1.2.3"
}
}
Apart from Darko's answer, esm and cjs export is also a possible reason for context to fail in a package. If you use the hook in esm and the provider in cjs, you will not get the value for that context.
I recently had a similar issue where I was trying to consume the value of a context inside my library components but using the provider (imported from the package) in the host app.
I managed to solve the issue just by making react and react-dom external and peerDependencies when bundling in rollup.
should your code of consumer be
<React.Fragment>
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
</React.Fragment>
as stated from the official react doc : https://zh-hant.reactjs.org/docs/context.html
when you define
you can use it like

Named import in React

In this line:
import React, { Component } from "react";
why the braces are only around Component and not also on 'React'?
Here's a great answer that explains default and named imports in ES6
Let's say we have a class named Foo that I want to import. If I want to get the default export, I would do:
import Foo from './foo.js';
If I wanted a specific function inside the foo file, I would use the curly braces.
import { fooFunction } from './foo.js';
Note, this isn't a React feature, but ES6. Likely you are using babel to transpile your code from ES6 to ES5.
To create something similar in react. Lets take this following example.
someobject.js
const someobject = {
somefunc1: ()=>console.log("hello 1"),
somefunc2: ()=>console.log("hello 2")
}
export default someobject;
app.js
import someobject, { somefunc1, somefunc2 } from "./someobject";
someobject.somefunc1(); //hello 1
someobject.somefunc2(); //hello 2
somefunc1(); //hello 1
somefunc2(); //hello 2
export defaul
In the React module the default export is the React object and it also has a named export Component1, something like this:
// assuming React and Component are predefined
export default React
export Component
Coincidentally Component is also available on the React object, so it is not necessary to import separately, although some people prefer your approach. For example this is possible:
// MyComponent.js
import React from 'react'
class MyComponent extends React.Component {}
More information about ES6 module syntax can be found here.
1 Note that actually the React library does not have a named export Component as in the example above, however Component is a property on the default export and so due to the way that ES6 packages are transpiled by Babel, this becomes a named export, the behaviour then being as in the example above.
Import react will import the default react package, Component with braces then specifies a particular element of the React package. React by default will not need braces as it is the default import package.
import React, { Component } from "react";
Hope this helps
That is because the default export module in the react library is React, and there can be only one default export, even though you can export many other components, but only one can be default. Other components of the React library can then be destructured.
React is a module containing different methods, When using just React word, you import the whole module, so you can use React.Component (in this case dot notation reference to a method inside the module).
So if you need to import method? you will use braces, why?
because you import method between many methods in one module, So it's able to increase & decrease, so you can import {a, b, c, r, w, q} that's methods inside one class or one module, So you can see that if you using
import {Component} from 'react';
Now you can use Component word direct without dot reference like react.component.
So React module is exported by default, Here I need all the React module and I will use it with all methods, {Component} here exported by name, I need specific method from React library not all react library
and please check it too When should I use curly braces for ES6 import?
in the import import React, { Component } from "react"; we put the Component in braces because it is not the default export. however React is,... look at the following example
import React from "react";
export const Fun1 = () => {
return (
<React.Fragment>
<h1>this is fun 1</h1>
</React.Fragment>
);
};
export const Fun2 = () => {
return (
<React.Fragment>
<h1>this is fun 2</h1>
</React.Fragment>
);
};
const Fun3 = () => {
return (
<React.Fragment>
<h1>this is fun 3</h1>
</React.Fragment>
);
};
export default Fun3;
if we save the above file under example.js we can import the components in exmpample.js file as
import Fun3, {Fun1, Fun2} from "example";
therefore Fun3 is the default export and the other components Fun1 and Fun2 are not

Organizing React Components

I am building a React App which consists of lot of smaller components. At present I am importing the components in app.js like this:
import Nav from './components/nav'
import ColorPicker from './components/colorPicker'
class App extends Component {
render() {
return (
<div>
<Nav />
<ColorPicker />
</div>
);
}
}
export default App;
Each component is a separate js file (nav.js, colorPicker.js). Is there anyway to just import everything in components folder so I don't have to explicitly specify importing of the components.
I'm not sure if there is a way to just import everything from a folder with one module per file, but you can make a kind of index.js file in which you would import everything that you will want, then:
export * from nav;
export * from colorPicker;
And then you only have to import your one index file from which you can: import {nav, colorPicker} from './components/things';
You always have to explicitly set the import of the components you use, but what you can do is decrease the amount of code typed by setting up an index file where you export all components you want to make available in said file, like this for example:
./components/index.js
import Component1 from './Component1';
import Component2 from './Component2';
import Component3 from './Component3';
export {
Component1,
Component2,
Component3,
};
Then import the needed components in the desired file like this:
./app.js
import { Component1, Component2 } from './components';
class App extends Component {
render() {
return (
<div>
<Nav />
<ColorPicker />
</div>
);
}
}
export default App;
Tree structure
app.js
components
+---
Component1.js
Component2.js
index.js
If you can add a babel plugin, you can use babel-plugin-wildcard, that is a plugin that makes exactly what you want.
Taken from NPM:
With the following folder structure:
|- index.js
|- dir
|- a.js
|- b.js
|- c.js
the following JS:
import * as Items from './dir';
will be compiled to:
const Items = {};
import _wcImport from "./dir/a";
Items.A = _wcImport;
import _wcImport1 from "./dir/b";
Items.B = _wcImport1;
import _wcImport2 from "./dir/c";
Items.C = _wcImport2;
meaning you will be able to access the items using Items.A and Items.B.
You can also selectively choose files using:
import { A, C } from "dir/*";
which in the above example would convert to:
import A from "./dir/a";
import C from "./dir/c";
The above is like doing:
import * as temp from "dir";
const { A, C } = temp;
Answer found inside this post.

Categories

Resources