I seem to be unable to render components using name strings. I wanted to be able to dynamically generate component name strings which have existing corresponding components that can be rendered.
Fiddle: http://jsfiddle.net/dhjxu5oL/
Instead of referencing the existing component and rendering it, React is rendering a custom element tag that is all lower case.
// HTML output
<componentname></componentname>
Very basic code that is failing:
// Main component to be rendered
var Parent = React.createClass({
render: function() {
var ChildName = 'Child';
return (
<ChildName />
);
}
});
// Dynamic subcomponent render
var Child = React.createClass({
render: function() {
return (
<div>
I am Child!
</div>
);
}
});
React.render(
<Parent/>,
document.body
);
Simple fix: just use Child
instead of 'Child'
– you want to pass an actual reference to the JS variable:
@spicyj I don't think that's what he is asking. I think he wants to be able to create a React element from a dynamically acquired name (string).
What he is looking for is:
React.createElement(ChildName, null)
instead of what he has (<ChildName />
)
React.createElement(ChildName, null)
and <ChildName />
are equivalent.
@spicyj EDIT: You're correct.
@spicyj @jsfb Both of those suggestions arrive at the same outcome. Neither of them are supporting the ability to dynamically generate name strings which reference existing components.
I wanted to store some ID value in a component prop and then use the name string to render the component.
What I am looking for is something almost like eval() or running window[nameString]()
.
var NameString = 'Component' + this.props.componentID;
return <NameString />
Updated fiddle with easy testing: http://jsfiddle.net/q3zwtosw/
As you probably know, it's best practice not to use eval
or look up globals on window
by string name as it tends to make code hard to reason about, but you can use those patterns with React too:
var name = 'Component' + this.props.componentID;
var Component = window[name]; // or eval(name)
return <Component />;
The key is to use a capitalized variable name (Component
) or else React will treat it as a built-in DOM element.
I am aware of evils of eval / window lookups, I just assumed/hoped React would support something like a secure scoped reference method as part of JSX transpiling. One could easily implement an immutable scoped object of valid component names and then compare strings to ensure that nothing malicious was going to be passed to the lookup method. It would greatly simplify dynamic component calls by not requiring massive switch or else if statements in front of render return statements.
@spicyj @jsfb Thanks!
Maybe you can do something like
var allMyComponents = {};
var Child = React.createClass(...);
allMyComponents['Child'] = Child;
// ...
var Component = allMyComponents[name];
which isn't a large overhead but is more reasonable than using globals. React tries to be simple and get out of your way instead of adding a lot of magic – you can then build whatever you want on top, just as you can in JS without React.
@charliegeiger89: what is your final solution for this issue?
I am having the same problem as yours. I have tried window[myStringVal], however, it fails somehow in Firefox.
The following code posted by spicyj seems not address the problem, because Child is unknown when the code is written. Wondering why React does not support variable string name as component name.
var allMyComponents = {};
var Child = React.createClass(...);
allMyComponents['Child'] = Child;
// ...
var Component = allMyComponents[name];
Came across this issue today, from a bit different point of view: configuration.
I'm creating an embeddable element that can receive configuration options - one of them being what react class to use when rendering.
Example:
<EmbeddableList>
<ListElement />
<ListElement />
<ListElement />
...
</EmbeddableList>
I want to be able to pass a listElementClass
prop to EmbeddableList
that changes what react class to use instead of ListElement
when rendering. In EmbeddableList
render method I call React.createElement(this.props.listElementClass, someProps)
.
There are no problems if the react class object gets passed in as listElementClass
. But I'm working with react-rails
, and the intended use case is calling react_component 'EmbeddableList', { listElementClass: 'DifferentListElement' }
Rails helper, and that limits me to prop types that can be represented in JSON. That means I'm receiving string 'DifferentListElement'
as a prop, instead of the actual class.
Currently I'm using eval(this.props.listElementClass)
to get the actual react class. It works, but leaves a bad taste in my mouth. I was hoping React.createFactory
would be of use here, but that also wants the actual class, instead of string.
React doesn't have a global registry so that won't ever be built-in functionality.
You could have your own registry and just make sure each class registers in that, then you could access that inside EmbeddableList
, then you can just use that. For example:
// DifferentListElement.js.jsx
var DifferentListElement = React.creaceClass({});
ReactComponentRegistry.set('DifferentListElement', DifferentListElement);
// EmbeddableList.js.jsx
var EmbeddableList = React.creaceClass({
render() {
var ListElement = ReactComponentRegistry.get(this.props.listElementClass)
return <Whatever><ListElement>...
}
});
This is essentially what Ben described above, but with a registry object. Either way it would require a different global object be available.
The other thing is that you may be setting window.DifferentListElement
when creating the class because that's the default behavior of the asset pipeline. So when you're doing eval(this.props.listElementClass)
, you could actually just write window[this.props.listElementClass]
and would be a slightly less bad taste.
React.createFactory
is only for when you aren't using JSX, and you still need the class object itself to pass in.
I came twice in a situation that I needed a dynamic use of the proper component.
A suggestion below:
The React team could create a helper function, for example: _React.getComponentByName(name: string)_, that returns the component that has the same name as the input string:
import MyComponent from './MyComponent.js';
const myComponents = [];
myComponents[0] = React.getComponentByName(`MyComponent`);
In that way, we can easily implement the following use case of rendering the proper component based on props received:
import MyComponent1 from './MyComponent1.js';
import MyComponent2 from './MyComponent2.js';
import MyComponent3 from './MyComponent3.js';
const myComponentsList = [];
for (let i of [1, 2, 3]) {
myComponentsList[i] = React.getComponentByName(`MyComponent${i}`);
}
Then, in JSX:
render()
{
return (
<div>
<div>
Use the proper Component below, based on the incoming props:
</div>
{
myComponentsList[this.props.componentId]
}
</div>
)
}
@af7 If that's what you want then you need to provide the mapping yourself, there is nothing React can do for you here. React works with regular classes and classes are not magically registered with React.
import MyComponent1 from './MyComponent1.js';
import MyComponent2 from './MyComponent2.js';
import MyComponent3 from './MyComponent3.js';
const myComponentsList = [
MyComponent1,
MyComponent2,
MyComponent3
];
const myComponentsListByName = {
MyComponent1: MyComponent1,
MyComponent2: MyComponent2,
MyComponent3: MyComponent3
};
/* es6 syntax */
const myComponentsListByName = {
MyComponent1,
MyComponent2,
MyComponent3
};
What @syranide said. Then in render you can do:
// Needs to start with a capital letter
var Type = myComponentsList[this.props.componentId];
...
<Type />
If you can live with having all your components in one module, then this works pretty well:
import * as widgets from 'widgets';
var Type = widgets[this.props.componentId];
...
<Type />
From @rmoskal said, I've been able to put something together. I'm also using react-rails
but with a different setup you can find here if you're on Rails as well it will allow you use import statements and use npm packages : Rails + Browserify + React + ES7
I created a selectr_option_templates
file where I will put the different components I want to use as templates which looks like this :
class ProductColor extends React.Component {
render() {
return (
<div className="product-color">
<span style={{background: this.props.option.value}}></span> {this.props.option.name}
</div>
);
}
}
ProductColor.propTypes = {
option: React.PropTypes.object
}
module.exports = {
"ProductColor": ProductColor
}
The important part then is to use module.exports
to make the mapping happen.
Then in your parent module you can do :
import * as templates from 'components/selectr/selectr_option_templates';
var optionComponent = templates[this.props.optionComponent]
<optionComponent />
I pass "ProductColor" in my this.props.optionComponent
This way I can get my class name from the props, previously set with react_component
helper with react-rails
. Obviously this will work however you get your string class name from.
This is the best solution I found so far, also for some reason eval(this.props.optionComponent)
throws an error so I can't use it.
@afilp, this solution is not favorable to Tree Shaking
Most helpful comment
Simple fix: just use
Child
instead of'Child'
– you want to pass an actual reference to the JS variable:http://jsfiddle.net/dhjxu5oL/1/