How can I use dynamic react class element in render? - reactjs

I understand the question is not clear, so the description will help.
So, I have some react components like,
var LandingPage = React.createClass({
render: function() {
return <div>
This is the landing page.
</div>
}
})
and another component like
var FirstContent = React.createClass({
render: function() {
return <div>
This is first content page
</div>
}
})
Now I have a controller where I decide which one I need to render by passing a value in props, something like this -
var Contoller = React.createClass({
render: function() {
var inside = "";
if (this.props.pageName == "LandingPage") {
inside = <LandingPage />;
} else if (this.props.pageName == "FirstContent") {
inside = <FirstContent />;
}
return <div>
{inside}
</div>;
}
})
Now instead, I want to do something like, use the this.props.pageName inside the tag directly, so that I don't have to write if else for every time ad some new alternate content. Should be something like this -
var Contoller = React.createClass({
render: function() {
return <div>
<"this.props.pageName" /> //comment - <LandingPage /> if this.props.pageName = "LandingPage"
</div>;
}
})

The map of pageName to actual Component has to exist somewhere, because other than the default HTML elements (like div) React needs the class object reference to render a component. A string wont do.
How you manage this map is up to you, but I've used an object below.
Further complicating this is the JSX compilation step, which doesn't work with dynamic content. You will have to use the actual JS calls in your Controller to get this working.
Here is a codepen demonstrating this.
class LandingPage extends React.Component {
render() {
return <div> This is the landing page. </div>;
}
}
class FirstContent extends React.Component {
render() {
return <div> This is the first content page. </div>;
}
}
const map = {
LandingPage: LandingPage,
FirstContent: FirstContent
};
class Controller extends React.Component {
render() {
return React.createElement(map[this.props.pageName]);
}
}
React.render(<Controller pageName={'LandingPage'} />, document.body);
All that being said, I think you are building a router. You can use react-router in memory mode to perform routing without using the URL. Rolling your own setup here may be more work than it's worth.

The map does exist in the example by Tyrsius: you can use window[this.props.pageName]. Though it's better not to expose your components to the window object. And it may not work at all if you're using CommonJS for your React components.
If you don't need to build a name of several parts, why don't you just pass the component itself instead of a string? Either as a property or, better, as a child:
class Controller extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
React.render(<Controller><FirstContent/></Controller>, document.body);

Related

ReactJS: Parent child partial update

I'm having a bit of a trouble getting my head around how i would communicate between my parent and child components in my specific use case.
I have a child component that renders some points using an external lib.
What I'm currently doing is implementing componentDidMount/Update and use the external lib to render the points in this.props.points (which is an array provided by the parent component).
Rendering the points involves looping through them and call something like ExternalLib.addPoint(point).
What i would like to do, instead of looping through all the points everytime this.props.points changes, is making the parent component add (or remove) individual points to the child component.
Is it React friendly to call something like this.refs.myChild.addPoint(point) in the parent component?
Are there other techniques to achieve something like this?
Update
Here's some code (https://jsfiddle.net/69z2wepo/61366/):
const ExternalLib = {
addPoint: function(el, point) {
var li = document.createElement("li");
li.innerHTML = point;
el.appendChild(li);
}
}
class Child extends React.Component {
componentDidMount() {
this.renderPoints();
}
componentDidUpdate() {
this.renderPoints();
}
renderPoints() {
const el = this.refs.points;
el.innerHTML = '';
this.props.points.forEach(function(point) {
ExternalLib.addPoint(el, point);
});
}
render() {
console.log('render')
return (
<div>
<ul ref="points"></ul>
</div>
);
}
}
class Parent extends React.Component {
constructor() {
super();
this.state = {
points: []
};
}
addPoint() {
const points = this.state.points.slice();
points.push((new Date()).getTime());
this.setState({
points: points
});
}
render() {
return (
<div>
<button onClick={this.addPoint.bind(this)}>Add Point</button>
<Child points={this.state.points} />
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('container')
);
This is simplified because in this example I could generate the markup directly making use of map and leveraging React's partial DOM update - the external lib does some extra stuff that's not in this question's scope.
Thanks!
If you want to interfere with React rendering process, you can code shouldcomponentupdate.
In your children, if you make shouldcomponentupdate return false and call ExternalLib.addPoint(point), it should do the job:
shouldComponentUpdate(nextProps){
//comparePoints() is a function able to find if a new point is present in the list, comparing existing used points with new one
var newPoint = comparePoints(this.props.points, nextProps.points)
if(newPoint){
ExternalLib.addPoint(newPoint);
//forbide re-render
return false;
}
//enable other update
return true;
}

Rendering Firebase Data in React

I'm looking to render some firebase data to the HomeFeed component. I update the state in the componentDidMount method. You can see what that looks like below. It's an array. Should I just map over that using the map function? How do I access the specific info like "title", "link", "type", etc. to be able to render it?
Thanks a lot!
var React = require('react');
var Rebase = require('re-base');
var base = Rebase.createClass("https://nimbus-8ea70.firebaseio.com/");
// TODO: Render Firebase data to screen.
// Home
// <Home />
var HomeContainer = React.createClass({
render : function() {
return (
<div className="homeContainer">
<HomeFeed />
</div>
);
}
});
// Home Feed
// <HomeFeed />
var HomeFeed = React.createClass({
componentDidMount: function() {
base.fetch('article', {
context: this,
asArray: true,
then(data){
console.log(data);
this.setState({
feed: data
})
}
});
},
getInitialState: function() {
return {
feed: []
}
},
render : function() {
return (
<div className="homeFeed">
{/* Use map function here? */}
</div>
);
}
});
module.exports = HomeContainer;
render will run whenever state has been changed (unless you modify this behavior with, say, shouldComponentUpdate) so as long as you use setState properly your component will automatically update when its state changes.
If you're asking specifically how to turn an array into something that render understands, then yes, map is a very common way to do that. It might look something like this:
render : function() {
return (
<div className="homeFeed">
{this.state.feed.map(function(ea){
return <div>{ea.someProperty}</div>
})}
</div>
);
}
Note that you have to wrap ea.someProperty in curly braces because you're basically inserting JSX inside of a JavaScript expression inside of even more JSX. This kind of nested JSX/Expression/JSX structure is something you'll have to get comfortable with in React I'm afraid.
More about array.map

Access parent context when using this.props.children in React

Given the following, is it possible to access the parent context rather than the containers from a child (non-react component) element?
The example logs container, ideally it would log parent. I would like for Parent to be self contained, not to have it's state managed by its container.
var Container = React.createClass({
getInitialState: function () {
return {
context: 'container'
}
},
render: function () {
return (
<Parent>
<a href="#" onClick={function () {console.log(this.state.context);}.bind(this)}>click me</a>
</Parent>
);
}
});
var Parent= React.createClass({
getInitialState: function () {
return {
context: 'parent'
}
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
If there is another pattern for handling this, please share as well.
Note: To be clear, I understand how the this keyword works and why the above example works as it does. The example is simply meant to illustrate the problem.
You can import some React helpers for that:
var React = require('react')
...
var children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
context: this.state.context
})
})
render() {
return <div>{ children }</div>
}
...
Then your child component will have this.props.context which will be the string 'parent', but this must be a React component, as this needs to refer to the component using the parent prop
var YourComponent = React.createClass({
render() {
return (
<a href="#" onClick={() => console.log(this.props.context)}>
click me
</a>
)
}
})
------
var Parent = require('./Parent')
var YourComponent = require('./YourComponent')
...
render() {
return <Parent><YourComponent /></Parent>
}
I do not know about the first part of your question, but since you commented about dynamically creating components, here's how I do it:
You can set a state variable in the constructor of the class and its parent:
if (typeof this.state == 'undefined') {
this.state = {
componentsToRender: <div></div>
};
}
Then in the parent component, in the componentDidMount() function:
var componentsToRender = [];
if ([conditional]) {
// some logic so you know which component to render
componentsToRender.push(<customChildComponentToRender key={} />);
}
else {
componentsToRender.push(<otherComponentToRender key={} />);
}
this.setState({
componentsToRender: <div>{componentsToRender}</div>
});
Make sure to put a key (lines 4 and 7 of the second code block) or React will scream at you.
In response to your initial question, I would watch this video from the ReactJS Conference 2015 to get more of the heart behind a container. After hearing what the guys at Facebook say (who have radical views on containers!), you might want to rethink the design to make your container more of a data layer.
I would check out THIS article from the react website. I think it might give you some intuition on solving your problem.
As a general rule of thumb, I try and only use this.state to handle internal UI state of a specific component. Everything else is passed via props. If you're needing the full context of a component, I would either pass it as a prop or checkout something like flux or redux which will help you manage state between components.

React context doesn't transfer when using a component as prop

I'm using react-bootstrap's ModalTrigger to show a field-heavy modal (based on react-bootstrap's Modal), which means sending it a bunch of props:
<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
Click here to open
</ModalTrigger>
The parent component that creates the trigger has the fields/values passed in via props, and the parent component of that component has it passed as props as well, by the top-level component that actually holds the data. Both are basically pipes, which is a classic childContext scenario, except that it doesn't work. Here's a simplified version of what I've tried:
var MyModal = React.createClass({
contextTypes : {foo : React.PropTypes.string},
render : function() {
return (
<Modal {...this.props} title="MyTitle">
<div className="modal-body">
The context is {this.context.foo}
</div>
</Modal>
);
}
});
var Content = React.createClass({
childContextTypes : {foo: React.PropTypes.string},
getChildContext : function() {return {foo : "bar"}},
render : function() {
return (
<ModalTrigger modal={<MyModal/>}>
<span>Show modal</span>
</ModalTrigger>
)
}
});
The modal pops up with "The context is", without showing the actual context.
I believe this is happening because the prop sent to ModalTrigger is already rendered/mounted somehow, but I'm not sure why. To the best of my understanding, the owner of MyModal is the Content component, which means that the context should be ok, but this is not the case.
Some more information: I already tried passing {...this.props} and context={this.context} to MyModal with no success. Also, maybe relevant, ModalTrigger uses cloneElement to make sure the modal's onRequestHide prop points to the trigger's hide function.
So what am I missing here? :/
React.cloneElement will change the owner of the element when the ref prop is overriden, which means context will not be passed from the previous owner. However, this does not seem to be the case with ModalTrigger.
Note that the owner-based approach won't work altogether in React 0.14, since context will be passed from parent to child and not from owner to ownee anymore. ModalTrigger renders its modal node prop in another branch of the DOM (See OverlayMixin). Thus, your Modal component is not a child nor a descendant of your Content component and won't be passed child context from Content.
As for solving your problem, you can always create a component whose sole purpose is to pass context to its children.
var PassContext = React.createClass({
childContextTypes: {
foo: React.PropTypes.string
},
getChildContext: function() {
return this.props.context;
},
render: function() {
return <MyModal />;
},
});
To use it:
<ModalTrigger modal={<PassContext context={this.getChildContext()}/>}>
As Matt Smith hinted, it turns out that react-bootstrap already includes a very similar approach to forwarding context via ModalTrigger.withContext. This allows you to create a ModalTrigger component class which will forward its context to its modal node prop, no matter its position in the VDOM tree.
// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
foo: React.PropTypes.String
});
There is a much better way of passing context on to your "portal" type components which render their children into a different container outside the React tree.
Using "renderSubtreeIntoContainer" rather than "render" will pass the context into the subtree as well.
It can be used like so:
import React, {PropTypes} from 'react';
import {
unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer,
unmountComponentAtNode
} from 'react-dom';
export default class extends React.Component {
static displayName = 'ReactPortal';
static propTypes = {
isRendered: PropTypes.bool,
children: PropTypes.node,
portalContainer: PropTypes.node
};
static defaultProps = {
isRendered: true
};
state = {
mountNode: null
};
componentDidMount() {
if (this.props.isRendered) {
this._renderPortal();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isRendered && !this.props.isRendered ||
(prevProps.portalContainer !== this.props.portalContainer &&
prevProps.isRendered)) {
this._unrenderPortal();
}
if (this.props.isRendered) {
this._renderPortal();
}
}
componentWillUnmount() {
this._unrenderPortal();
}
_getMountNode = () => {
if (!this.state.mountNode) {
const portalContainer = this.props.portalContainer || document.body;
const mountNode = document.createElement('div');
portalContainer.appendChild(mountNode);
this.setState({
mountNode
});
return mountNode;
}
return this.state.mountNode;
};
_renderPortal = () => {
const mountNode = this._getMountNode();
renderSubtreeIntoContainer(
this,
(
<div>
{this.props.children}
</div>
),
mountNode,
);
};
_unrenderPortal = () => {
if (this.state.mountNode) {
unmountComponentAtNode(this.state.mountNode);
this.state.mountNode.parentElement.removeChild(this.state.mountNode);
this.setState({
mountNode: null
});
}
};
render() {
return null;
}
};
This is an example of a portal I use in my production app Casalova that render context properly into their children.
Note: this API is undocumented and is likely to change in the future. For now, though, it's the right way to render context into portal components.

Creating a non-rendered wrapper component in react.js

I'm wanting to create a React component that does a security check and if that passes it'll render out the children of it, if it fails then it won't render anything.
I've scaffolded out a component like so:
var RolesRequired = React.createClass({
permitted: roles => ...,
render: function () {
if (!this.permitted(this.props.roles)) {
return null;
}
return this.props.children;
}
});
The usage I was planning would be like this:
<RolesRequired roles={['admin']}>
<h1>Welcome to the admin</h1>
<div>
Admin stuff here
</div>
</RolesRequired>
How would you return all the children from the RolesRequired component?
I came up with this solution:
var RolesRequired = React.createClass({
permitted: roles => ...,
render: function () {
if (!this.permitted(this.props.roles)) {
return null;
}
return <div>{this.props.children}</div>;
}
});
What I'm doing is wrapping the children being returned in a <div> but I'm having to add an unwanted/unneeded DOM element to achieve it.
I think higher order components (HOC) are also a good candidate for this. You can basically wrap any component in HOC that defines some behaviour and decides if it should render a wrappe.
Nicest way to do this would be if you're using a ES2015 transpiler with some ES2016 features enabled (namely decorators):
function withRoles(roles) {
return function(Component) {
return class ComponentWithRoles extends React.Component {
constructor(props) {
super(props)
// Not sure where the data to get your roles about current user?
// from but you could potentially to that here if I'm getting your point
// and also setup listeners
this.state = { currentUser: 'admin' }
}
validateRoles() {
// you have access to the ``roles`` variable in this scope
// you can use it to validate them.
return true;
}
render() {
if (this.validateRoles()) {
return <Component {...this.props} />;
)
} else {
return <div>Nope...</div>;
}
}
}
}
}
// You can then use this on any component as a decorator
#withRoles({ showOnlyFor: [ 'admin' ] })
class AdminOnlyComponent extends React.Component {
render() {
return <div> This is secert stuff </div>
}
}
I've used ES2016 features because I think it's nicer to get the point across but you can implement that with just a simple function wrapping, here's a gist by one of the React core members on the topic of HOC:
https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775

Resources