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>
Related
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'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>
)
}
})
Based on https://facebook.github.io/react/docs/two-way-binding-helpers.html , I am trying to make a multi-checkbox-status todo that displays, at start, a textarea. I am trying to follow the model, and where it has:
var WithLink = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {message: 'Hello!'};
},
render: function() {
return <input type="text" valueLink={this.linkState('message')} />;
}
});
I have:
var Todo = React.createClass(
{
mixins: [React.addons.LinkedStateMixin],
getInitialState: function()
{
var that = this;
return {
children: [],
description: '',
id: 0
};
},
and, in the same class:
render: function()
{
var that = this;
return (
<table>
<tbody>
{that.state.children}
</tbody>
<tfoot>
<td>
<textarea className="description"
placeholder=" Your next task..."
onChange={that.onChange} name="description"
valueLink={that.linkState('description')}
id="description"></textarea><br />
<button onClick={that.handleClick}
id="save-todo">Save</button>
</td>
</tfoot>
</table>
);
}
When it renders, there's nothing but an h1 and a jQuery bgstretcher on the page. Chrome's Inspect Element doesn't turn up what I expected and had earlier, a TEXTAREA and a BUTTON inside a table. The log doesn't display any either errors or messages.
At some point I expect further work so that a description is handed off to a new child and re-initialized, but I'm not there yet.
Suggestions for what I should do for it to display?
I've looked through the active questions that are slightly related to my question but I didn't come to a successful solution. I hope someone can help me.
I have a simple react baby code on js fiddle: http://jsfiddle.net/kb3gN/10313/
Explanation of my code can be found below the code here:
var Test = React.createClass({
getInitialState: function(){
return{
data: [
['Charlie', 10],
['Bello', 20],
['Wuffi', 15]
]
}
},
render: function(){
return(
<div>
<MakePOS data={this.state.data} />
<MakeTable />
</div>
);
}
});
var MakeTable = React.createClass({
getInitialState: function(){
return{
active: 0,
weight: 0
}
},
render: function(){
return(
<table>
<thead>
<tr>
<td>Type</td>
<td>Amount</td>
</tr>
</thead>
<tbody>
<tr>
<td>Chicken</td>
<td>some</td>
</tr>
<tr>
<td>Carotts</td>
<td>some</td>
</tr>
<tr>
<td>Apple</td>
<td>some</td>
</tr>
</tbody>
</table>
);
}
});
var MakePOS = React.createClass({
handleClick: function(){
// update state of MakeTable Component to active = id of the clicked element
// also update state of MakeTable Component to weight = data-weight value
},
render: function(){
var POS = this.props.data.map(function(i){
console.log(i[0]+' '+i[1]+' kg');
return <button onClick={this.handleClick} data-weight={i[1]} id={i[0]}>{i[0]}</button>;
});
return(<div className="testDiv">{POS}</div>);
}
});
React.render(<Test />, document.getElementById('foodApp'));
To explain my code:
the state array in the Test Component represents the users input, in this case he choose to fill in 3 pets and their weight.
Dynamically to his input the buttons are created in the MakePOS Component.
Now what I want to do is, handle the Click event on those buttons and affect the state of the MakeTable Component in Order to deal with the values hidden behind that buttons.
I want to dinamically change the table rows amounts depending on the weight of the pet.
I hope its understandable.
Thanks for your help
edit
The facebook documentation mentions something similar, but I dont really get the hint of it.
In their example they are simply calling the onClick function in the same component. I can't find any solution for child component state altering of parent components :(
http://facebook.github.io/react/tips/communicate-between-components.html
You should move your handleClick function to the component which is managing the state you want to affect, in this case Test, and then pass it down as a callback via props. Since the click is also affecting active and weight, they should be kept in the state of Test as well, so the handleClick can easily change their state.
var Test = React.createClass({
getInitialState: function(){
return{
data: [
['Charlie', 10],
['Bello', 20],
['Wuffi', 15]
],
active: 0,
weight: 0
}
},
changeWeight: function(weight){
// do whatever actions you want on click here
console.log('I am in the main Component ');
console.log(weight);
},
render: function(){
return(
<div>
<MakePOS data={this.state.data} changeWeight={this.changeWeight} />
<MakeTable active={this.state.active} weight={this.state.weight}/>
</div>
);
}
});
var MakeTable = React.createClass({
getInitialState: function(){
return{
active: 0,
weight: 0
}
},
render: function(){
return(
<table>
<thead>
<tr>
<td>Type</td>
<td>Amount</td>
</tr>
</thead>
<tbody>
<tr>
<td>Chicken</td>
<td>some</td>
</tr>
<tr>
<td>Carotts</td>
<td>some</td>
</tr>
<tr>
<td>Apple</td>
<td>some</td>
</tr>
</tbody>
</table>
);
}
});
var MakePOS = React.createClass({
handleClick: function(weight){
this.props.changeWeight(weight);
},
render: function(){
var POS = this.props.data.map(function(i){
console.log(i[0]+' '+i[1]+' kg');
return <button onClick={this.handleClick.bind(this,i[1])} key={i[0]} id={i[0]}>{i[0]}</button>;
}.bind(this));
return(<div className="testDiv">{POS}</div>);
}
});
React.render(<Test />, document.getElementById('foodApp'));