Currently i m working on creating react components, and i need to repeat the child components, based on this.props.value of parent component.
I am struggling to find any good examples.
here is my code
var LiComponent = React.createClass({
render:function(){
return(
<div>
<span>{this.props.label}</span>
<span>{this.props.value}</span>
</div>
);
}
});
var StatsComponent = React.createClass({
getInitialState: function(){
return{
value: this.props.value || []
}
},
componentDidMount: function(){
var data = this.state.value,
columnName = data[0],
data = data.slice(1),
values = data.map(function (o) { return o.value; }),
labels = data.map(function (o) { return o.label; });
},
shouldComponentUpdate: function(nextProps, nextState){
var data = nextProps.value,
labels = data.map(function (o) { return o.label; }),
values = data.map(function (o) { return o.value; });
return false;
},
render: function(){
return (
<div style={style}>
<LiComponent>
</LiComponent>
</div>
);
}
});
now, i want to repeat the LiComponent according to the this.props.value of Stats Component. How should i do that?
You can push LiComponents to an array, and render these.
Like this,
render: function(){
var rows = this.props.value.map(function(row) {
//Add props to your LiComponent just as you would normally.
return <LiComponent />;
});
return (
<div style={style}>
{rows}
</div>
);
}
Related
If I have a component:
var OuterComponent = React.createClass( {
render: function() {
return (
<div>
<MiddleComponent>
<Child parentFunctionShouldBe={this.middleComponentFunction} />
</MiddleComponent>
</div>
);
}
});
How do i pass the function that lives in the middle component? The issue is that this refers to the OuterComponent, not the MiddleComponent. I don't want it to be a static function, but an instance function.
Another option would be to use a Higher-order Component (HoC). This is a good choice if MiddleComponent is primarily adding markup around or capabilities to Child.
var MiddleComponent = function (Wrapped) {
var Component = React.createClass({
middleComponentFunction: function () {},
render: function () {
return (
<Wrapped {...this.props} parentFunctionShouldBe={this.middleComponentFunction} />
);
}
});
return Component;
}
var WrappedChild = MiddleComponent(Child);
var OuterComponent = React.createClass( {
render: function() {
return (
<div>
<WrappedChild passToChild="value" />
</div>
);
}
});
Further, you can customize how the integration occurs if you want to use MiddleComponent with other types of components.
var MiddleComponent = function (config, Wrapped) {
var prop = config.prop || 'parentFunctionShouldBe';
var Component = React.createClass({
middleComponentFunction: function () {},
render: function () {
var extraProps = {};
extraProps[prop] = this.middleComponentFunction;
return <Wrapped {...this.props} {...extraProps} />;
}
});
return Component;
}
// same result as above but configurable for other use cases
var WrappedChild = MiddleComponent({prop: 'parentFunctionShouldBe'}, Child);
var OuterComponent = React.createClass( {
render: function() {
return (
<div>
<WrappedChild passToChild="value" />
</div>
);
}
});
You can add a ref to a component, and call the method from the this.refs.componentRef:
var OuterComponent = React.createClass( {
callMethod: function() {
this.refs.middleComponent.middleComponentFunction();
},
render: function() {
return (
<div>
<MiddleComponent ref="middleComponent">
<Child parentFunctionShouldBe={this.callMethod} />
</MiddleComponent>
</div>
);
}
});
You can do something like this
var OuterComponent = React.createClass( {
render: function() {
return (
<div>
<MiddleComponent>
<Child />
</MiddleComponent>
</div>
);
}
});
var MiddleComponent = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired
},
render: function() {
return (
<div>
{React.cloneElement(this.props.children, {parentFunctionShouldBe: this.middleComponentFunction})}
</div>
)
},
middleComponentFunction: function() {
...
}
});
I'll be very grateful who can help me with this line
I've this:
var Country = React.createClass({
render:function(){
return(
<nav>
<h2>list of country:</h2>
{ this.props.country}
</nav>
)
}
})
var Jugador =React.createClass({
componentWillMount: function(){
var pais;
var self = this;
$.getJSON("https://restcountries.eu/rest/v1/all", function(data){
for(pais in data)
{
console.log(pais, data[pais].name);
return(
<Country key={i} country={self.render(data[pais].name)}> </Country>
)
}
})
},
})
and it does not work, and appear this error
Uncaught Invariant Violation: createClass(...): Class specification must implement a render method.
Your Jugador component needs to implement a render method.
In React each component must have render method, you should implement it. I've refactored your code and now it look like this
var Country = React.createClass({
render: function() {
return <div>
{ this.props.country }
</div>
}
})
var Countries = React.createClass({
getInitialState: function () {
return { countries: [] };
},
componentWillMount: function(){
$.getJSON("https://restcountries.eu/rest/v1/all", function (data) {
this.setState({ countries: data })
}.bind(this))
},
render: function () {
var countries = this.state.countries.map(function (el, i) {
return <Country key={i} country={ el.name } />
}, this);
return <nav>
<h2>list of country:</h2>
<div>{ countries }</div>
</nav>;
}
})
Example
I'm trying to build a react nested list component that is fully draggable with react-dnd. I have a menu component which contains item components, and each item can also contain other item components. Unfortunately I don't know how to make a node with children draggable or any of its children nodes. Right now I have gotten nodes without childrendraggable, which is a good start, but then fails with:
TypeError: node is undefined
any time I try to drag any children. I used the react-dnd simple sortable example as a reference, but that doesn't contain any nesting.
Here is what I have so far:
Menu.js
//React DnD
var DragDropContext = require('react-dnd').DragDropContext;
var HTML5Backend = require('react-dnd-html5-backend');
//Item
var Item = require('./item');
var Menu = React.createClass({
getInitialState() {
return {
currentNode: this.props.data,
items: [],
};
},
_clicked(child) {
this.setState({
currentNode: child,
});
},
componentDidMount() {
this._updateData();
},
_updateData: function(list) {
var $this = this;
if(_.isUndefined(list)){
var children = $this.props.data.children;
}
else{
if(_.isEmpty(list.children)){
var children = null;
}
else{
var children = list.children;
}
}
if(children != null){
var items = children.map(function(item, i) {
return (<Item
key={item.id}
id={item.id}
child={item}
showChildren={this.props.showFirstChildren}
clickable={true}
onClick={$this._updateData}
swapItems={this.swapItems}
/>);
}.bind(this));
if(!_.isUndefined(list)){
$this.setState({
currentNode: list,
});
}
$this.setState({
items: items,
});
}
},
compareItems: function(item1, item2){
return item1.position - item2.position;
},
swapItems: function(id1, id2) {
var $this = this;
var items = this.state.currentNode.children;
var item1 = items.filter(function(i){return i.id === id1})[0];
var item2 = items.filter(function(i){return i.id === id2})[0];
var item1Pos = item1.position;
item1.position = item2.position;
item2.position = item1Pos;
items.sort(this.compareItems);
var newItems = items.map(function(item, i) {
return (<Item
key={item.id}
id={item.id}
child={item}
showChildren={this.props.showFirstChildren}
clickable={true}
onClick={$this._updateData}
swapItems={this.swapItems}
/>);
}.bind(this));
this.setState({
items: newItems,
});
},
render() {
return (
<div>
{this.state.currentNode.name}
<ul>
{this.state.items}
</ul>
</div>
);
},
});
module.exports = DragDropContext(HTML5Backend)(Menu);
Item.js
var React = require('react');
var ReactDnD = require('react-dnd');
var ItemTypes = {
ITEM: 'item'
};
var itemSource = {
beginDrag: function (props) {
return {
id: props.id,
};
}
};
var itemTarget = {
hover: function(props, monitor) {
var draggedId = monitor.getItem().id;
if (draggedId !== props.id) {
props.swapItems(draggedId, props.id);
}
}
};
var Item = React.createClass({
propTypes: {
connectDropTarget: React.PropTypes.func,
connectDragSource: React.PropTypes.func,
isDragging: React.PropTypes.bool,
id: React.PropTypes.any,
swapItems: React.PropTypes.func,
},
_clicked(child) {
this.props.onClick(child);
},
render() {
var $this = this;
var connectDragSource = $this.props.connectDragSource;
var isDragging = $this.props.isDragging;
var connectDropTarget = $this.props.connectDropTarget;
var child = this.props.child;
var childrenWrapper = null;
if (child.children && this.props.showChildren) {
var children = child.children.map(function(item, i) {
return (<Item
key={item.id}
id={item.id}
showChildren={false}
child={item}
onClick={$this._clicked}
clickable={true}
//passing these items along because otherwise it throws errors
connectDropTarget={$this.props.connectDropTarget}
connectDragSource={$this.props.connectDragSource}
swapItems={$this.props.swapItems}
/>);
});
childrenWrapper = (
<ul>
{children}
</ul>
);
}
var style = {
cursor: 'move',
opacity: this.props.isDragging ? 0 : 1
};
if (child.children && this.props.clickable) {
return connectDragSource(connectDropTarget(
<li style={style} key={child.id} ><a onClick={$this._clicked.bind(null, child)}>{child.title}</a>{childrenWrapper}</li>
));
} else {
return connectDragSource(connectDropTarget(
<li style={style} key={child.id}><a>{child.title}{childrenWrapper}</a></li>
));
}
},
});
var DragSourceDecorator = ReactDnD.DragSource(ItemTypes.ITEM, itemSource,
function(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
});
var DropTargetDecorator = ReactDnD.DropTarget(ItemTypes.ITEM, itemTarget,
function(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
};
});
module.exports = DropTargetDecorator(DragSourceDecorator(Item));
It seems as though the nested items are not being passed dragSource and dropTarget correctly because they are not wrapped when I look at the structure in my React DevTools browser extension, but I'm not sure because I feel react-dnd should take care of that. Thanks in advance, any guidance will be greatly appreciated.
You don’t have to pass “connectDropTarget” and “connectDropTarget” to the nested Items. Instead, you have to wrap - inside Item`s render method instead of
return (<Item
key={item.id}...
write
return (<WrappedItem
key={item.id}…
instead of
module.exports = DropTargetDecorator(DragSourceDecorator(Item));
write
var WrappedItem = DropTargetDecorator(DragSourceDecorator(Item));
module.exports = WrappedItem;
I've got the same problem when I try to manipulate tree before drag end. I.e. change leafs order before dragEnd() throw me the same error. But plain list works perfectly.
I stop to change tree during DnD (our designer said - it is not good UX: jumping to mouse pointer subtree).
So my solution: during DnD just change classes for target and source sub-trees (but do not change leafs order), like:
.source-dnd {
opacity: 0.2;
}
.target-dnd-add {
border: 1px dotted #aaa;
}
.target-dnd-after {
border-bottom: 1px dotted #aaa;
}
.target-dnd-before {
border-top: 1px dotted #aaa;
}
Also don't forget about monitor.isOver({ shallow: true })
I get warnings about missing keys on GridRow in the following code. I have read about it but I do not find a way to apply it to the react component? It seems like I have to do it in the final render method but I don't have any data there?
var data1 = {"Columns":[{"Title":"Title1","HTMLClass":"g1_Title"},{"Title":"Title2","HTMLClass":"g2_Title"},{"Title":"Title3","HTMLClass":"g3_Title"}],"Rows":[{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]},{"Cells":["Cell0","Cell1","Cell2"]}]};
var GridRow = React.createClass({
render: function() {
var data = [], columns;
if(this.props.columns){
for(var i = 0; i < this.props.columns.length; i++){
data.push({
HTMLclass: this.props.columns[i].HTMLClass,
content: this.props.cells[i]
})
}
}
columns = data.map(function(col) {
return (
<div className={col.HTMLclass}>{col.content}</div>
);
}.bind(this));
return (
<li>
{columns}
</li>
);
}
});
var GridHead = React.createClass({
render: function() {
if(this.props.data){
var cell = this.props.data.Title;
var htmlClass = this.props.data.HTMLClass;
}
return (
<div className={htmlClass}>{cell}</div>
);
}
});
var GridList = React.createClass({
render: function() {
if(this.props.data){
var header = this.props.data.Columns.map(function (columns, i) {
return (
<GridHead data={columns} key={i} />
);
});
var row = this.props.data.Rows.map(function (row, i) {
return (
<GridRow columns={data1.Columns} cells={row.Cells} key={i} />
);
});
}
return (
<ul>
<li>{header}</li>
{row}
</ul>
);
}
});
var GridBox = React.createClass({
loadCommentsFromServer: function() {
var xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
getInitialState: function() {
return { data: this.props.initial };
},
render: function(){
return (
<GridList data={data1} />
);
}
});
Warning : Each child in an array should have a unique "key" prop.
Check the render method of GridRow. See
fb.me/react-warning-keys for more information.
Just add the key to your column div, React just needs some unique identifier so it can keep it's Virtual DOM diff up-to-date.
The key can be anything, just as long as it's unique.
columns = data.map(function(col, i) {
return (
<div className={col.HTMLclass}>{col.content} key={i}></div>
);
}.bind(this));
The key should only be set to the index if there are no other options.
I am trying to update my table with a ajax call by using this.setState({ data: data }); but the datatable is not redrawn with the new data? (I can see that new data is received)
var GridRow = React.createClass({
render: function() {
var data = [], columns;
if(this.props.columns){
for(var i = 0; i < this.props.columns.length; i++){
data.push({
HTMLclass: this.props.columns[i].HTMLClass,
content: this.props.cells[i]
})
}
}
columns = data.map(function(col, i) {
return (
<td className={col.HTMLclass} key={i}>{col.content}</td>
);
}.bind(this));
return (
<tr>
{columns}
</tr>
);
}
});
var GridHead = React.createClass({
render: function() {
if(this.props.data){
var cell = this.props.data.Title;
var htmlClass = this.props.data.HTMLClass;
}
return (
<th className={htmlClass}>{cell}</th>
);
}
});
var GridList = React.createClass({
render: function() {
var tableClass = this.props.tableClass;
if(this.props.data){
var header = this.props.data.Columns.map(function (columns, i) {
return (
<GridHead data={columns} key={i} />
);
});
var row = this.props.data.Rows.map(function (row, i) {
return (
<GridRow columns={data1.Columns} cells={row.Cells} key={i} />
);
});
}
return (
<table className={tableClass}>
<tr>{header}</tr>
<tbody>
{row}
</tbody>
</table>
);
}
});
var GridPager = React.createClass({
handleClick: function(e) {
e.preventDefault();
this.props.onPaging();
},
render: function() {
return(
<a href='#' onClick={this.handleClick}>Paging</a>
);
}
});
var gridPage = 0;
var GridBox = React.createClass({
loadCommentsFromServer: function() {
var xhr = new XMLHttpRequest();
gridPage++;
xhr.open('get', this.props.url + '/?pageNr=' + gridPage, true);
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
handlePaginSubmit: function(comment) {
this.loadCommentsFromServer();
},
getInitialState: function() {
return { data: this.props.initialData };
},
render: function(){
var tableClass = this.props.tableClass;
return (
<div>
<GridList data={this.state.data} tableClass={tableClass} />
<GridPager onPaging={this.handlePaginSubmit} />
</div>
);
}
});
that = this
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
that.setState({ data: data });
}.bind(this);
In your code this is the context of the callback method where setState method is not available, but it's a callback and you don't get an error about it. Use the trick listed above so you have a reference to the right context.
setState() in react js ALWAYS causes rerender.
If your props are updating then better to use
componentWillReceiveProps(nextProps) {
this.props.data = nextProps.data;
}
react will render the component when state changes so you can use
this.setState(this.state)
If you want to update your render() method reads from something other than this.props or this.state, you'll need to tell React when it needs to re-run render() by calling
forceUpdate()