I'm getting this React.js error I'd love to know how to fix it. Is there a way to call React.cloneElement on this?
Warning: Don't set .props.children of the React component . Instead, specify the correct value when initially creating the element or use React.cloneElement to make a new element with updated props. The element was created by Seven.
Here's a full working example.
Here's my code:
var Thead = React.createClass({
displayName: 'Thead',
propTypes: function () {
return {
children: React.propTypes.node
}
},
componentWillMount: function () {
this.childTr = containsElement('tr', this.props.children)
this.props.children = reconcileChildren('th',
(this.childTr) ? this.childTr.props.children : this.props.children,
this.props.data
)
},
render: function () {
return (
<thead>
{this.childTr ? <tr {...this.childTr.props}>
{this.props.children}
</tr> : <tr>
{this.props.children}
</tr>}
</thead>
)
}
})
As #fuyushimoya said in the comments is discouraged to alter props (which I didn't know) I set the children to this.children instead.
var Thead = React.createClass({
displayName: 'Thead',
propTypes: function () {
return {
children: React.propTypes.node
}
},
componentWillMount: function () {
this.childTr = containsElement('tr', this.props.children)
this.children = reconcileChildren('th',
(this.childTr) ? this.childTr.props.children : this.props.children,
this.props.data
)
},
render: function () {
return (
<thead>
{this.childTr ? <tr {...this.childTr.props}>
{this.children}
</tr> : <tr>
{this.children}
</tr>}
</thead>
)
}
})
Related
I have a Table component which I want to render custom component based on my data (which is an array of objects) also I need my component (I mean Table component) to have two props, one is the data object and another one is an array of objects which each object has two properties: a title and a function that render different component based on the data key. for example if the key in data object is fullName, I need to render a paragraph tag or if key is avatar, I need to return an image tag and so on.. let me show in code:
const Table = () => {
const data= [
{
id: 1,
avatar: 'blah blah blah',
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
];
const cols = [
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td>{el.id}</td>;
});
},
},
{
title: 'Avatar',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td><span>{el.avatar}</span></td>;
});
},
},
];
return (
<div className='table-responsive' style={{width: '95%'}}>
<table className='table table-borderless'>
<thead>
<tr>
//here I need to show my headers...
{cols.map((el, index) => {
return <th key={index}>{el.title}</th>;
})}
</tr>
</thead>
<tbody>
//here I need to fill my table with components.
<tr className='table-row'>
{cols.map((el, index) => {
return el.componentToRender(data);
})}
</tr>
</tbody>
</table>
</div>
);
};
and the problem is table show only headers but data cells are empty. how can I achieve this?
This is one of the objects that you have defined.
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td>{el.id}</td>;
});
},
}
if you look closely , you will see that you are not returning anything from the function. The return keyword that you have used, goes back to the map method. So to fix the problem, you will need to change the code like this:
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
return rowsArr.map((el, index) => {
return el.id;
});
},
}
and tbody logic needs to change as well:
<tbody>
<tr className='table-row'>
{cols.map((el, index) => {
return <td key={index}>{el.componentToRender(rows)}</td>;
})}
</tr>
</tbody>
I refactored the component rendering. Instead of looping trough data in every component render, I created a data.map inside <tbody>, which creates a <tr> for every data entry (every object inside data array) and then renders the needed component based on the title.
Beware! title in cols should have the same naming case as the one in data. (for example both should be Avatar or avatar)
import "./styles.css";
const Table = () => {
const data= [
{
ID: 1,
Avatar: 'blah blah blah', // Capitalized like the Avatar in cols
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
{
ID: 2,
Avatar: 'blah blah blah2',
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
];
const cols = [
// refactored the components
{
title: 'ID',
component(content) { return <td>{content}</td> }
},
{
title: 'Avatar',
component(content) { return <td><span>{content}</span></td> }
},
];
return (
<div className='table-responsive' style={{width: '95%'}}>
<table className='table table-borderless'>
<thead>
<tr>
{cols.map((el, index) => {
return <th key={index}>{el.title}</th>;
})}
</tr>
</thead>
<tbody>
{/* completely changed here */}
{data.map((entry, entryindex) => {
return <tr className='table-row'>
{cols.map((el, index) => {
return el.component(data[entryindex][el.title])
})}
</tr>
})}
</tbody>
</table>
</div>
);
};
export default function App() {
return (
<div className="App">
<Table />
</div>
);
}
See it live in Codesandbox
I'm facing a problem making a little application
for the Price Calculation of some Products in React. This is how my application looks like:
What I need now is to have a global total (sum of the partial total of the ListItem components), but i don't know how to do that with React. I tried using the same "onChange" events of the smallest component (ListItem) to trigger an event on the parent like:
handleChange:function(event){
this.props.onChange(event.target);
const target = event.target;
const name = target.name;
const value = target.value;
this.setState({
[name]: value
});
},
but in this way only this event has been triggered and didn't update the state.
Probably I'm missing something.
Recapping what I need is to pass the partial total, calculated in the ListItem, in the parent component, Table, so that I can calculate the global total.
function Header(){
return (
<h1>Calcolo costo prodotti</h1>
)
}
var ListItem = React.createClass({
getInitialState: function(){
return {name: this.props.value.product.name, costo: this.props.value.product.costo, quantita: this.props.value.product.quantita, totale: 0}
},
render: function(){
return(
<tr>
<td><input type="text" name="name" value={this.state.name} onChange={this.handleChange} placeholder="Nome..."/></td>
<td><input type="text" name="costo" value={this.state.costo} onChange={this.handleChange} placeholder="Costo unitario..."/></td>
<td><input type="text" name="quantita" value={this.state.quantita} onChange={this.handleChange} placeholder="Quantità..."/></td>
<td className="total">{this.calcoloTotale()}</td>
</tr>
)
},
handleChange:function(event){
const target = event.target;
const name = target.name;
const value = target.value;
this.setState({
[name]: value
});
},
calcoloTotale: function(){
var Ltotale = this.state.costo * this.state.quantita;
this.setState({totale: Ltotale});
return Ltotale;
}
});
var Table = React.createClass({
getInitialState: function(){
return { totale: 0 }
},
render: function(){
return(
<div>
<table>
<tr>
<th>Nome</th>
<th>Prezzo</th>
<th>Quantità</th>
<th>Totale</th>
</tr>
{this.props.items.map((prodotto) =>
<ListItem key={prodotto.id} value={prodotto}/>
)}
</table>
</div>
)
}
});
var AddNewRow = React.createClass({
render: function(){
return(
<div>
<button onClick={this.props.onClick}>+</button>
Aggiungi prodotto
</div>
)
}
});
var Calculator = React.createClass({
getInitialState: function(){
return {
counter: 2, lists: [{id: "0", product: {name: "Esempio 1",costo: "25",quantita: "3"}}, {id: "1", product: {name: "Esempio 2",costo: "32",quantita: "4"}}]
}
},
render: function(){
return (
<div className="container">
<Header />
<Table items={this.state.lists} ids={this.counter}/>
<AddNewRow onClick={this.addRow}/>
</div>
)
},
addRow: function(){
this.setState({counter: this.state.counter + 1});
var listItem = {id: this.state.counter, product:{name:"", costo: "", quantita: ""}};
var allItem = this.state.lists.concat([listItem])
this.setState({lists: allItem});
}
});
ReactDOM.render(
<Calculator />,
document.body
);
EDIT 1:
var totalVec = new Array();
function Header(){
return (
<h1>Calcolo costo prodotti</h1>
)
}
var ListItem = React.createClass({
getInitialState: function(){
return {name: this.props.value.product.name, costo: this.props.value.product.costo, quantita: this.props.value.product.quantita}
},
render: function(){
return(
<tr>
<td><input type="text" name="name" value={this.state.name} onChange={this.handleChange} placeholder="Nome..."/></td>
<td><input type="text" name="costo" value={this.state.costo} onChange={this.handleChange} placeholder="Costo unitario..."/></td>
<td><input type="text" name="quantita" value={this.state.quantita} onChange={this.handleChange} placeholder="Quantità..."/></td>
<td className="total">{this.calcoloTotale()}</td>
</tr>
)
},
handleChange:function(event){
const target = event.target;
const name = target.name;
const value = target.value;
this.setState({
[name]: value
});
this.props.updateGlobalTotal();
},
calcoloTotale: function(){
var Ltotale = this.state.costo * this.state.quantita;
totalVec[this.props.value.id] = Ltotale;
return Ltotale;
}
});
var Table = React.createClass({
getInitialState: function(){
return { totale: 0 }
},
render: function(){
return(
<div>
<table>
<tr>
<th>Nome</th>
<th>Prezzo</th>
<th>Quantità</th>
<th>Totale</th>
</tr>
{this.props.items.map((prodotto) =>
<ListItem key={prodotto.id} value={prodotto} updateGlobalTotal={this.updateGlobalTotal}/>
)}
</table>
<h1>{this.state.totale}</h1>
</div>
)
},
componentDidMount: function(){
var total = 0;
for(var i = 0; i < this.props.ids; i++){
total += totalVec[i];
}
this.setState({totale: total});
},
updateGlobalTotal: function(){
var total = 0;
for(var i = 0; i < this.props.ids; i++){
total += totalVec[i];
}
this.setState({totale: total});
}
});
var AddNewRow = React.createClass({
render: function(){
return(
<div>
<button onClick={this.props.onClick}>+</button>
Aggiungi prodotto
</div>
)
}
});
var Calculator = React.createClass({
getInitialState: function(){
return {
counter: 2, lists: [{id: "0", product: {name: "Esempio 1",costo: "25",quantita: "3"}}, {id: "1", product: {name: "Esempio 2",costo: "32",quantita: "4"}}]
}
},
render: function(){
return (
<div className="container">
<Header />
<Table items={this.state.lists} ids={this.state.counter}/>
<AddNewRow onClick={this.addRow}/>
</div>
)
},
addRow: function(){
this.setState({counter: this.state.counter + 1});
var listItem = {id: this.state.counter, product:{name:"", costo: "", quantita: ""}};
var allItem = this.state.lists.concat([listItem])
this.setState({lists: allItem});
}
});
ReactDOM.render(
<Calculator />,
document.body
);
You may want to check out Redux as Ksyqo mentioned. However, for such needs it may not be entirely what you need as it would require you to apply a wide variety of boilerplate and cognitive overhead at this particular moment when you already have existing app code written.
For smaller project(s), one might find that you can, with better results, use MobX alternative as it is a bit easier to implement especially in existing applications. It is also a lot easier to reason about. It will work pretty much out of the box, with a little bit of magic involved.
Whatever the decision is, this graph holds true for both Redux and MobX, and illustrates the problem of global state vs parent-child chained state (the former is obviously much cleaner) :
You can add a function in the parent that changes the state of that component, then send that function as a prop to the child.
var ListItem = React.createClass({
getInitialState: function(){
return {name: this.props.value.product.name, costo: this.props.value.product.costo, quantita: this.props.value.product.quantita, totale: 0}
},
...
calcoloTotale: function(){
var Ltotale = this.state.costo * this.state.quantita;
this.props.updateGlobalTotal(Ltotale);
this.setState({totale: Ltotale});
return Ltotale;
}
});
var Table = React.createClass({
getInitialState: function(){
return { totale: 0 }
},
updateGlobalTotal: function(value){
this.setState({totale: this.state.totale + value})
}
render: function(){
return(
<div>
<table>
<tr>
<th>Nome</th>
<th>Prezzo</th>
<th>Quantità</th>
<th>Totale</th>
</tr>
{this.props.items.map((prodotto) =>
<ListItem key={prodotto.id} value={prodotto} updateGlobalTotal={this.updateGlobalTotal}/>
)}
</table>
</div>
)
}
});
I think Redux is made for you.
It helps you pass properties between components that do not necessarily have a child/parent relationship. Basically, it creates a global state that you can access from anywhere.
There is a lot to say about redux (we don't call it react/redux for no reason), and this is a must-have if you want to do something powerful with React.
You can find more on the Redux documentation !
My React JS file is below:
The logic behind this:
1.) CreateTable renders CreateColumns, CreateRows, & ChangeResults
On the first render, CreateRows is empty, but once the component mounts, a fetch() call is made to update the rows, page re-renders and I have my table
2.) ChangeResultscomponent is loaded which creates an input box. State for num_of_rows to an empty string (placeholder, not sure if I even need to do this).
When I input some number into the input field and hit click, onClick runs updatePage, which calls the function updateRows (in CreateTable), that then changes the state of people_per_page. I have the console.log() to verify that this is actually happening, and it prints out as expected.
I thought that since CreateRows inherits people_per_page, and I'm changing the state of people_per_page, it would cause a re-render...but nothing is happening.
Can anyone see why that might be?
var CreateTable = React.createClass({
getInitialState: function(){
console.log('initial state loaded')
return {
'table_columns' : ['id','email', 'first', 'last', 'title', 'company', 'linkedin', 'role'],
people_per_page: 34
}
},
updateRows: function(rows) {
console.log(rows)
this.setState(
{people_per_page: rows},
function() {
console.log(this.state.people_per_page)
}
)
},
render: function(){
return (
<div>
<ChangeResults updateRows = {this.updateRows} />
<table>
<CreateColumns columns={this.state.table_columns} />
<CreateRows num_of_rows = {this.state.people_per_page} />
</table>
</div>
)
}
});
var ChangeResults = React.createClass({
getInitialState: function(){
return {
num_of_rows : ''
}
},
handleChange: function(e) {
this.setState({
'num_of_rows' : e.target.value
});
},
updatePage: function(){
this.props.updateRows(this.state.num_of_rows);
},
render: function(){
return (
<div>
Number of people per page: <br />
<input type="text" onChange = {this.handleChange} />
<button onClick={this.updatePage}> Update Page </button>
</div>
)
}
})
var CreateColumns = React.createClass({
render: function(){
var columns = this.props.columns.map(function(column, i){
return (
<th key={i}>
{column}
</th>
)
});
return (
<thead>
<tr>
{columns}
</tr>
</thead>
)
}
});
var CreateRows = React.createClass({
getInitialState: function() {
return {
'people':[],
}
},
componentDidMount: function(){
console.log('componentDidMount running')
this.createRow();
},
createRow : function(){
console.log('starting fetch')
fetch('http://localhost:5000/search', {
method: 'POST',
body: JSON.stringify({
people_per_page: this.props.num_of_rows
})
})
.then(function(response) {
return response.json()
})
.then((responseJson) => {
return this.setState({'people' : responseJson.people })
});
},
render: function(){
var rows = this.state.people.map(function(row, i){
return (
<tr key={i}>
<td>{row['id']}</td>
<td>{row['email']}</td>
<td>{row['first']}</td>
<td>{row['last']}</td>
<td>{row['title']}</td>
<td>{row['company']}</td>
<td>{row['linkedin_url']}</td>
<td>{row['role']}</td>
</tr>
)
})
return (
<tbody>
{rows}
</tbody>
)
}
});
ReactDOM.render(<CreateTable />, document.getElementById('content'));
In <CreateRows />, componentDidMount is only called for the first render, when the component is 'mounted' on the page. After that, you need to fetch new data in componentDidUpdate or somewhere else in the application.
I saw some questions speaking about similar issues but somehow I still do not manage to solve my issue so here I am asking for your kind help. I am pretty new to React and would like to send a function from a Parent to a child and then use it from the Child but somehow when I want to use it it says
Uncaught TypeError: Cannot read property 'props' of undefined"
Edited Code after first answers were helping:
var Menu = React.createClass({
links : [
{key : 1, name : "help", click : this.props.changePageHelp}
],
render : function() {
var menuItem = this.links.map(function(link){
return (
<li key={link.key} className="menu-help menu-link" onClick={link.click}>{link.name}</li>
)
});
return (
<ul>
{menuItem}
</ul>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function() {
console.log('help');
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
Pass a value from Menu function and recieve it in the changePageHelp function and it works.
var Menu = React.createClass({
render : function() {
return (
<div>
{this.props.changePageHelp('Hello')}
</div>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function(help) {
return help;
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="admin"></div>
For performance reasons, you should avoid using bind or arrow functions in JSX props. This is because a copy of the event handling function is created for every instance generated by the map() function. This is explained here: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
To avoid this you can pull the repeated section into its own component. Here is a demo: http://codepen.io/PiotrBerebecki/pen/EgvjmZ The console.log() call in your parent component receives now the name of the link. You could use it for example in React Router.
var Admin = React.createClass ({
_changePageHelp : function(name) {
console.log(name);
},
render : function () {
return (
<div>
<div id="menu-admin">
<Menu changePageHelp={this._changePageHelp} />
</div>
</div>
);
}
});
var Menu = React.createClass({
getDefaultProps: function() {
return {
links: [
{key: 1, name: 'help'},
{key: 2, name: 'about'},
{key: 3, name: 'contact'}
]
};
},
render: function() {
var menuItem = this.props.links.map((link) => {
return (
<MenuItem key={link.key}
name={link.name}
changePageHelp={this.props.changePageHelp}
className="menu-help menu-link" />
);
});
return (
<ul>
{menuItem}
</ul>
);
}
});
var MenuItem = React.createClass ({
handleClick: function() {
this.props.changePageHelp(this.props.name);
},
render : function () {
return (
<li onClick={this.handleClick}>
Click me to console log in Admin component <b>{this.props.name}</b>
</li>
);
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
I am currently trying to learn react by building a simple app that works with a JSON array by calling a certain API. I would then like to show the results of the array in a table and have the ability to click one of the rows and get the data from that row to update another part of the page.
I have successfully called the API and am showing the correct data in the table but I am struggling to figure out how to show the data after the click in another part of the page. I created a click handler event and can log information to the console but cant figure out how I would show this data on the page for the relevant row that is clicked.
So I currently have this in my page:
<div class="container"></div>
<script type="text/jsx">
var ShowData = React.createClass({
getInitialState: function() {
return { data: [] };
},
componentDidMount: function() {
$.get(this.props.source, function(data) {
if (this.isMounted()) {
this.setState({
data: data
});
}
}.bind(this));
},
handleClick: function(i) {
console.log('You clicked: ' + this.state.data[i].event + this.state.data[i].name);
},
render: function() {
var self = this;
return (
<table className="m-table">
<thead>
<tr>
<th>Event</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{this.state.data.map(function(type, i){
return (
<tr>
<td title="Type" onClick={self.handleClick.bind(this, i)} key={i}>{type.event}</td>
<td title="Type">{type.name}</td>
</tr>
)
})}
</tbody>
</table>
);
}
});
React.render(
<ShowData source="url of api" />,
document.getElementById('container')
);
</script>
Below this script is another container that I would like to show the results when the table row is clicked.
So to summarize, I want to display the data from the API call in a table, upon clicking on one of the table rows I want to take the table row data and format it in another container on the page.
I'd move both the table-component and the display area-component into a parent component that has the responsibility of managing state. The parent component will pass a callback for handling row clicks down to the table, something along the lines of the below edits to your code example. (Heads-up: haven't slept in the past 40+ hours when writing this, due to travel..)
<div class="container"></div>
<script type="text/jsx">
var TableComponent = React.createClass({
handleClick: function(i) {
this.props.clickHandler(i);
},
render: function() {
var self = this;
return (
<table className="m-table">
<thead>
<tr>
<th>Event</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{this.props.data.map(function(type, i){
return (
<tr>
<td title="Type" onClick={self.handleClick.bind(this, i)} key={i}>{type.event}</td>
<td title="Type">{type.name}</td>
</tr>
)
})}
</tbody>
</table>
);
}
});
var DetailsArea = React.createClass({
render: function() {
var rowDetails = this.props.rowDetails;
return <div>{rowDetails.name}</div>;
}
});
var ParentComponent = React.createClass({
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
$.get(this.props.source, function(data) {
if (this.isMounted()) {
this.setState({
data: data
});
}
}.bind(this));
},
rowClickHandler: function(rowIndex) {
this.setState({selectedRow: rowIndex});
},
render: function() {
var tableData = this.state.data;
return (
<TableComponent data={tableData} clickHandler={rowClickHandler} />
<DetailsArea rowDetails={tableData[this.state.selectedRow]} />
}
});
React.render(
<ParentComponent source="url of api" />,
document.getElementById('container')
);
</script>