import or require React components dynamically - javascript

I'm trying to import / require components dynamically, but somehow when I do it React complains. The require function does find it, but React throws an error saying it is missing some functions 't' etc.. All of this in an electron app.
I have a wizard setup (that is working, but not so elegant I think), where each page has it's own layout and jsx component. If I'd like to add a new page, I don't want to manage x-number of files, and at the moment I have to due to the setup I have currently. Below you can find what I want to achieve and what I'm doing now to achieve it. If there are any suggestions, code smells or better options please let me know as I'm quite new to React and ES2015 (as I'm from a C# background).
What I'm trying to achieve
export default class WizardPageContainer extends Component {
// Constructor
constructor(props) {
super(props);
}
// Render
render() {
const WizardPage = require(`./path/to/${this.props.step.id}`);
return (
<WizardPage step={this.props.step} />
);
}
}
How I'm currently doing it : which means I have to declare the imports / files first on top of the "WizardPageContainer" component.. Which means extra work and prone to errors/forgetting things. I should add, this code is working now ok, but I don't think this is elegant/future proof:
/* PAGES */
import WizardPage_Welcome from './pages/0.wizard.welcome';
import WizardPage_SystemCheck from './pages/1.wizard.systemCheck';
import WizardPage_SignIn from './pages/2.wizard.signIn';
import WizardPage_ExamCode from './pages/3.wizard.examCode';
import WizardPage_TakeExamination from './pages/4.wizard.takeExamination';
import WizardPage_Close from './pages/5.wizard.close';
const pages = [
WizardPage_Welcome,
WizardPage_SystemCheck,
WizardPage_SignIn,
WizardPage_ExamCode,
WizardPage_TakeExamination,
WizardPage_Close
];
/*/********************************************************************///
/* ******************************************************************** */
/* COMPONENT */
export default class WizardPageContainer extends Component {
// Constructor
constructor(props) {
super(props);
}
// Render
render() {
const WizardPage = pages[`${this.props.step.id}`];
return (
<WizardPage step={this.props.step} />
);
}
}
/*/********************************************************************///

I think it is about the "default". i have problem like this. Can you check this code;
https://github.com/robeio/robe-react-admin/blob/master/src/app/HasAuthorization.jsx#L10
Also you can check the example usage;
https://github.com/robeio/robe-react-admin/blob/master/src/app/HasAuthorization.jsx#L26

Your const pages needs to be an object, not an array.
You can see a working version I made of this here:
https://github.com/Frazer/meteor-react-nav/blob/master/lib/jsx/App.jsx

Best advice: Use Webpack to handle your imports, it's way more efficient than we could ever be.

Related

React Javascript - Visual Studio Code doesn't auto-complete object properties

I have a React HOC that propagate an instance of a class to the children.
import React from "react";
import ObjContext from "../../context/Obj/ObjContext";
const withObj = (Component) => (props) => (
<ObjContext.Consumer>
{(obj) => <Component {...props} obj={obj} />}
</ObjContext.Consumer>
);
export default withObj;
Now, if in one of the child, I start coding, my code editor (VS Code Studio) doesn't display the properties of the object.
When I do props.obj. the editor doesn't show me all the stuff which is inside the object.
Instead, if I do const obj = new Obj() directly, I can see them.
Why is that? Is impossible to see the data which is inside the object that is propagated from a HOC and received via props?
Any workaround?
Thank you.
As said in the comments, the real answer here is TypeScript.
To see how your vs code editor would react with typescript, you can quickly do:
// Inside child component:
import Obj from 'path/to/obj'
...
export class ChildComponent extends React.Component {
this.obj: Obj;
constructor(props) {
super(props);
this.obj = this.pros.obj;
this.obj.something() // something would be proposed by ide
}
...
}
(Ofc the vs code will growl saying that types are not eligible in a .js file and you'd need .tsx instead, but for the purpose of taking a look at how TS would help with the auto-completion, its fine.

Is there a way to execute code from a ES6 class file before it is even referenced it?

I'm not an expert on JS and I have inherited a React app with dozens and dozens of components.
I want to do something that in Java is trivial but I can't find the way in ES6.
I want to build a registry of most of those components and I'm looking for an approach to have each class register itself so it can be looked up dynamically by a key.
So I build a registry class like this
let registry = [];
export default class Registry {
static register(component) {
if (!registry.find(x => x.key == component.key))
registry.push(component);
}
}
And on each component class, I tried to register itself doing something like this:
import Registry from './Registry.jsx';
import React from 'react';
export default class Component extends React.Component {
... body of the component ...
}
Registry.register( { key: 'ComponentX', component: Component });
But it doesn't work because Registry is undefined.
In Java would be something like:
class Component {
....
static {
Registry.register('ComponentX',Component.class);
}
....
}
The only alternative I found is to create a static list of all components but that chokes with our current distributed approach to development
Thanks a lot
Edit
Thanks guys. I'd managed to get a step closer to the solution.
Using this:
let registry = new Map();
export function register(key, component) {
return registry.set(key, component);
}
import { register } from './Registry.jsx';
export default class Component extends React.Component {
static entry = register(key, component);
... body of the class ...
}
Using this scheme, the register function is invoked but the registry variable is undefined. I just need to find out how to have it defined at the time of the invocation of the register function.

How to wrap every exported comopnent with HOC?

I need to add to ALL of my React function components the possibility to add [data-test-id] attribute for testing purposes. To achieve that I created withTestId() HOC which adds optional prop testId to wrapped component and when it's defined it adds [data-test-id] to final HTML.
So when I define component like:
<ExampleComponent testId="example" />
it returns:
<div data-test-id="example" />
The only problem I have is to apply it to every component without the necessity to wrap it individually in every component. So instead of writing code like:
function ExampleComponent() { ... }
export default withTestId(ExampleComponent)
I would like to wrap all of my exports in my index.ts file, which right now looks like this:
export { default as ExampleComponent } from "./ExampleComponent";
export { default as ExampleComponent2 } from "./ExampleComponent2";
...
How can I achieve this?
I see two ways of doing this; One dynamic way, making the user-code of your library a bit more convoluted. with you being able to change the implementation easily and another one with a bit more boilerplate code, keeping the user-code as it is.
I haven't tested their behavior regarding tree-shaking when bundling the code.
Using destructing in user-code
This allows to add / remove things from your main component export file without having to worry about additional boilerplate in your library. The higher-order-component can be switched on/off easily. One caveat: The user code needs to use destructuring to retrieve the components.
Your new index.ts file would look like this, while I've called your previous index.ts file components.ts in the same directory:
import * as RegularComponents from "./components";
import withTestId from "./with-test-id";
const WithTestIdComponents = Object
.keys(RegularComponents)
.reduce((testIdComps, key) => {
return {
...testIdComps,
[key]: withTestId(RegularComponents[key])
};
}, {});
export default WithTestIdComponents;
To use it in your application code:
import MyComponents from "./components/tested";
const { Component1, Component2, Component3, Component4 } = MyComponents;
This uses the default export to make it look like you have all components in one place, but since you cannot destructure exports directly, you need this second step to get the correct components out of it.
Add boilerplate to the export file
Since there is an index.ts file with all the components exported in the library, one could import/rename each component and re-export them with withTestId and their name:
import withTestId from "./with-test-id";
import { default as TmpComponent1 } from "./component1";
import { default as TmpComponent2 } from "./component2";
import { default as TmpComponent3 } from "./component3";
import { default as TmpComponent4 } from "./component4";
export const Component1 = withTestId(TmpComponent1);
export const Component2 = withTestId(TmpComponent2);
export const Component3 = withTestId(TmpComponent3);
export const Component4 = withTestId(TmpComponent4);
This way, imports can be used as before:
import {
Component1,
Component2,
Component3,
Component4
} from "./components";
I'd argue that using index files already is some kind of boilerplate and this approach adds to it. Since the user code does not need any changes, I'd favor this approach.
In one of our projects, we have used a custom takeoff script to create this kind of boilerplate for us, whenever we generate a new component.
Examples
Here is a code sandbox to see both approaches.

How does import work with react?

My broader question is does an import of a module get shared between two components and why?
First what do I know about import. You can import in two different ways.
1.
At the top of your file which loads the imported module into a variable which you then can use.
import Highcharts from './highcharts'
// create a chart
Highcharts.Chart()
2.
Or dynamically anywhere in your code which returns a promise:
import('./highcharts').then((response) => {
// create chart
response.Chart();
});
But there is this weird behavior I don't understand when using import with react. If I have the following component:
import React, {Component} from 'react';
import Highcharts from 'highcharts/js/highcharts';
export default class Chart extends Component {
state = {
chartOptions: {
// my chart options and data
}
}
componentDidMount() {
if(this.props.extendFunc) {
import('highcharts/js/modules/funnel.src.js').then((funnelModule) => {
funnelModule(Highcharts)
})
}
Highchart.Chart('myChart', this.state.chartOptions)
}
render() {
<div id="myChart" />
}
}
I use the component from above twice. Now there is this behavior that both components use the same import e.g. the import of Highcharts does not happen twice. I noticed this because with Highcharts there is the option of extending the functionality.
If I for example extend the functionality for Chart 1 by passing a prop to extend it, the functionality of Highcharts is also extended in Chart 2, although I didn't pass a prop to extend the functionality.
import React, {Component} from 'react';
import Chart from './Chart';
export default class Dashboard extends Component {
render() {
return (
<div>
<Chart extendFunc={true}> Chart 1 </Chart>
<Chart> Chart 2 </Chart>
</div>
)
}
}
What causes this behavior? Is this react or is this just the way import works? Are imports global for multiple instances of the same component? Or are imports of a node module the same for the whole application?
What causes this behavior? Is this react or is this just the way import works? Are imports global for multiple instances of the same component? Or are imports of a node module the same for the whole application?
This is the way imports work. When you import something for the first time, the file is run and the exported values from it are returned back to the one importing it. When something is imported again, those same exports are reused and returned. Node JS modules work the same way.
Sometimes this behavior is helpful, firstly for performance to avoid unnecessarily re-running the same file over again, and also if the module wants to store some internal state. For example, counting the number of times a function is called from anywhere in the application.
In cases like this, where you need a single instance of something for each script, modules will usually give you a way to actually make an instance of that thing. For example, I might have a logging module, which exports a Logger class, then I can make new instances of that class for each component, and configure each logger separately.
For your case, look in the docs to see if there's a way to make per-component instances of Highcharts and extend that individual instance with the functionality you need.
When you extend <Chart /> with a prop extendFunc it will be extended in your Chart Component and not in your "new" Component.
That means, if you call the component, it will always have the props you gave it, but you will not have to use them (if there are not set as required).

Using nodejs & react together (in electron)

I have a React component which I use in my electron application.
// #flow
import React, { Component } from 'react';
import Home from '../components/Home';
import SentenceView from '../components/SentenceView';
import Dictionary from '../components/Dictionary';
export default class HomePage extends Component {
render() {
const aTestArray = [1,2,3,4];
return (
<div>
<SentenceView numbers={aTestArray} />
</div>
);
}
}
I am giving my SentenceView component aTestArray. Now, in reality I won't have this array available but will need to create it myself. Since React is only the View, I do not want to create the array with React, but outsource this to a function I have written in nodejs. It creates an array and then returns it. So what (as of now) I have written as
const sentenceArray = ['Hello', 'Mister'];
would ideally look something like this then:
const sentenceArray = createTheArrayFunction(); //returns array
However, I do not know how I can make this work. I do not know how to give my React component access to function which is in a nodejs file, or how else I would connect the two. I am using electron, so it should be possible for me to use nodejs (somewhere somehow). But I just have no clue how to put these things together. I would be glad if someone could help me out.

Categories

Resources