Dynamically adding components in react - reactjs

I need to add components by rendering it in react:
<componentName ..... />
However, the name of component is not known and coming from a variable.
How I can render that?

You will need to store references to the dynamic components:
import ComponentName from 'component-name'
class App extends Component {
constructor(props) {
super(props)
this.components = {
'dynamic-component': ComponentName
}
}
render() {
// notice capitalization of the variable - this is important!
const Name = this.components[this.props.componentName];
return (
<div>
<Name />
</div>
)
}
};
render(<App componentName={ 'dynamic-component' } />, document.getElementById('root'));
See the react docs for more info.

You can create a file that exports all the different components
// Components.js
export Foo from './Foo'
export Bar from './Bar'
then import them all
import * as Components from './Components'
then you can dynamically create them based on a variable/prop:
render() {
// this.props.type = 'Foo'
// renders a Foo component
const Component = Components[this.props.type];
return Component && <Component />;
}

Okay, for me this worked:
import {Hello} from 'ui-hello-world';
let components = {};
components['Hello'] = Hello;
export default components;
and then in my class:
import customComps from '.....json';
....
let Component = Custom.default[customComps.componentName];
return (
<Component

Related

String from mobx store used as a react component name?

Using a map feels a bit repetative.
Any way to render react component using string passed down from mobx?
because when i have like 20 different dynamic components its quickly getting messy and repetative.
currently I'm using:
function ParentComponent(){
const compNames = {
component1: <component1/>,
component2: <component2/>,
}
const component = compNames[store.name];
return(
<div>
<MyComponentName type={type}/>
</div>
)
}
is anything shorter possible? for example
function ParentComponent(){
const {name: MyComponentName, type } = store
return(
<div>
<MyComponentName type={type}/>
</div>
)
}
the Parent component then imported into the index page.
// MobX store
import UserList from "./UserList";
import UserView from "./UserView";
#observable
component = {
"user-list": UserList,
"user-view": UserView
};
// observer component
#observer function UserComponent({type}) {
const Component = store.component[type];
return <Component />;
}
// display the component
<UserComponent type="user-list" />

How to avoid writing the same logic in components having a different layout?

How can I avoid writing the same code when two components share some same methods but have a different layout?
The sample components below have a method "renderLastItem" which uses prop "something" passed by the parent components.
I thought about using Higher Order Component Pattern but I'm not sure I I can pass props as an argument to Higher Order Component.
The sample code below is very simple, so in this sample code, I just need to use If statement and change the layout according to the type of components, but in real code, I have more codes and I want to avoid using if statement in order to change the layout according to the type of a component.
How can I avoid writing the same logic in multiple components?
ComponentA
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const propTypes = {};
const defaultProps = {};
class SampleA extends Component {
constructor(props) {
super(props);
}
renderLastItem() {
if(!this.props.something) {
return null;
}
return this.props.something[this.props.something.length - 1];
}
render() {
return (
<div>
<h1>Something</h1>
<p>{this.renderLastItem()}</p>
</div>
);
}
}
SampleA.propTypes = propTypes;
SampleA.defaultProps = defaultProps;
export default SampleA;
ComponentB
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const propTypes = {};
const defaultProps = {};
class SampleB extends Component {
constructor(props) {
super(props);
}
renderLastItem() {
if(!this.props.something) {
return null;
}
return this.props.something[this.props.something.length - 1];
}
render() {
return (
<div>
<ul>
<li>Something</li>
<li>Something else</li>
<li>{this.renderLastItem()}</li>
</ul>
</div>
);
}
}
SampleB.propTypes = propTypes;
SampleB.defaultProps = defaultProps;
export default SampleB;
You absolutely can pass props to a Higher-Order Component! A HOC is simply a function that takes a Component as an argument and returns another Component as a result. So you could create a Higher-Order withLastOfSomething Component just like this:
function withLastOfSomething(Component) {
return function({something, ...otherProps}) {
const item = something ? something[something.length - 1] : null;
return <Component item={item} {...otherProps} />;
}
}
Or with ES6 arrow functions, even more compactly like this:
const withLastOfSomething = (Component) => ({something, ...otherProps}) => {
const item = something ? something[something.length - 1] : null;
return <Component item={item} {...otherProps} />;
}
And then use it like this:
const SampleBWithLastOfSomething = withLastOfSomething(SampleB);
return (<SampleBWithLastOfSomething something={...} />);
You can separate the function that takes the passed props and executes the logic,
export default renderLastItem = (passedProps) => {
if(!passedProps) {
return null;
}
return passedProps [passedProps.length - 1]
}
then import it wherever you need, like this:
import renderLastItem from './somewhere'
export default class SampleA extends Component {
render() {
return (
<div>
<h1>Something</h1>
<p>{renderLastItem(this.props.something)}</p>
</div>
)
}
}

How to include the Match object into a ReactJs component class?

I am trying to use my url as a parameter by passing the Match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
My code:
import React, { Component } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
class InstructorProfile extends Component {
constructor(props, {match}) {
super(props, {match});
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
componentDidMount(){
axios.get(`/instructors`)
.then(response => {
this.setState({
instructors: response.data
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render(){
return (
<div className="instructor-grid">
<div className="instructor-wrapper">
hi
</div>
</div>
);
}
}
export default InstructorProfile;
React-Router's Route component passes the match object to the component it wraps by default, via props. Try replacing your constructor method with the following:
constructor(props) {
super(props);
this.state = {
instructors: [],
instructorID : props.match.params.instructorID
};
}
Hope this helps.
Your constructor only receives the props object, you have to put match in it...
constructor(props) {
super(props);
let match = props.match;//← here
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
you then have to pass that match object via props int a parent component :
// in parent component...
render(){
let match = ...;//however you get your match object upper in the hierarchy
return <InstructorProfile match={match} /*and any other thing you need to pass it*/ />;
}
for me this was not wrapping the component:
export default (withRouter(InstructorProfile))
you need to import withRouter:
import { withRouter } from 'react-router';
and then you can access match params via props:
someFunc = () => {
const { match, someOtherFunc } = this.props;
const { params } = match;
someOtherFunc(params.paramName1, params.paramName2);
};
Using match inside a component class
As stated in the react router documentation. Use this.props.match in a component class. Use ({match}) in a regular function.
Use Case:
import React, {Component} from 'react';
import {Link, Route} from 'react-router-dom';
import DogsComponent from "./DogsComponent";
export default class Pets extends Component{
render(){
return (
<div>
<Link to={this.props.match.url+"/dogs"}>Dogs</Link>
<Route path={this.props.match.path+"/dogs"} component={DogsComponent} />
</div>
)
}
}
or using render
<Route path={this.props.match.path+"/dogs"} render={()=>{
<p>You just clicked dog</p>
}} />
It just worked for me after days of research. Hope this helps.
In a functional component match gets passed in as part of props like so:
export default function MyFunc(props) {
//some code for your component here...
}
In a class component it's already passed in; you just need to refer to it like this:
`export default class YourClass extends Component {
render() {
const {match} = this.props;
console.log(match);
///other component code
}
}`

How to always initialise prop of this reactjs component?

I used react-router to define the following routes:
<Route
path="/plan"
components={App}
>
<Route
path="try/:planCategory"
components={PlanTrialContainer}
/>
<Route
path="pay/:planCategory"
components={PlanPurchaseContainer}
/>
</Route>
The idea is that: when a user visits url /plan, he will see a plan overview by default, otherwise the content will be decided by try/... or pay/...
Here is the App class
import React, { Component } from 'react';
import { Menu } from './Menu';
import PlansOverviewContainer from './PlansOverviewContainer';
export default class App extends Component {
render() {
const { children } = this.props.children;
let component = null;
if (children) {
component = children;
} else {
component = PlansOverviewContainer;
}
return (
<div>
<Menu />
{component}
</div>
);
}
}
I will get this error message when I try the /plan
TypeError: Cannot read property 'children' of null
App.render
/Users/antkong/dev/myproject/plan/app/scripts/components/App.js
If I change the line from
const { children } = this.props.children;
to
const children = this.props ? this.props.children : null;
It will work.
However my preferred version is the first version i.e. this.props should be initialised no matter what. How can I ensure prop is initialised? Is there any parameter to the Router component I can use?
You are destructuring children from this.props.children which is like trying to do:
this.props.children.children
Instead change it to:
const { children } = this.props;
You need to call the super-classes initializer like so:
import React, { Component } from 'react';
import { Menu } from './Menu';
import PlansOverviewContainer from './PlansOverviewContainer';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
const { children } = this.props.children;
let component = null;
if (children) {
component = children;
} else {
component = PlansOverviewContainer;
}
return (
<div>
<Menu />
{component}
</div>
);
}
}

React: dynamic import jsx

This question related to dynamically importing JSX files into React.
Basically we have one main component that dynamically renders other components based on a structure stored in a database. The dynamic components are stored in a subdirectory "./Components". We statically define the this as:
import CompA from './Components/CompA';
import CompB from './Components/CompB';
var components = {
'CompA': CompA,
'CompB': CompB
}
and we render them using:
var type = 'CompA'
var Component = components[type];
...
<Component />
While this works fine, it is a bit too static for us. We have 100+ components (CompA/CompBs) and statically define them does not work.
Is it possible to import all JSX files in "./Compontents" and populate the components-array?
And, what would be really (really) nice would be if we could send the "./Components" path as a prop to the main components. And the main component would use this path to import the .jsx files. Like this:
<MainComponent ComponentPath="./SystemComponents">
Would use "./SystemComponents" as path for .JSX files and:
<MainComponent ComponentPath="./UserComponents">
Would use "./UserComponents" as import path.
What about having a components/index.js with contents:
export CompA from "./comp_a";
export CompB from "./comp_b";
Then you do:
import * as Components from "./components"
then you would use as:
<Components.CompA />
<Components.CompB />
...
Hope this helps.
I doubt you can load anything when sending path through component props, loading of the file should then happen inside the React component lifecycle methods which is not something I would recommend.
As of React 16.6.0, we can lazy-load components and invoke them on-demand.
The Routing
// We pass the name of the component to load as a param
<Switch>
…
<Route path="/somewhere/:componentName" component={MyDynamicComponent} />
</Switch>
views/index.js
import { lazy } from 'react';
const SomeView = lazy(() => import('./SomeView'));
const SomeOtherView = lazy(() => import('./SomeOtherView'));
export { SomeView, SomeOtherView };
MyDynamicComponent.js
import React, { Suspense, Component } from 'react';
import { PropTypes } from 'prop-types';
import shortid from 'shortid'; // installed separately via NPM
import * as Views from './views';
class MyDynamicComponent extends Component {
render() {
const {
match: {
params: { componentName },
},
} = this.props;
const Empty = () => <div>This component does not exist.</div>;
const dynamicComponent = (() => {
const MyComponent = Views[`${componentName}View`]
? Views[`${componentName}View`]
: Empty;
return <MyComponent key={shortid.generate()} />;
})();
return (
<>
<Suspense fallback={<div>Loading…</div>}>{dynamicComponent}</Suspense>
</>
);
}
}
MyDynamicComponent.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({
componentName: PropTypes.string.isRequired,
}),
}),
};
export default MyDynamicComponent;
Usage
{items.map(item => (
<NavLink to={`/somewhere/${item.componentName}`}>
{item.name}
</NavLink>
))}
To complement #gor181's answer, I can add that exports must be this way:
export { default as CompA } from "./comp_a";
export { default as CompB } from "./comp_b";
Hope this might be helpful.

Resources