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 !
Related
I'm trying to create a menu where the user should create a character details but I'm having an issue to update the Options states through an input of a child.
var Name = React.createClass({
render: function(){
return(
<input type="text"/>
)
}
});
var Options = React.createClass({
getInitialState: function(){
return{
name: ''
}
},
render: function(){
return (
<div>
Name: <Name onChange={this.updateName} value={this.state.name} />
</div>
)
},
updateName: function(evt){
this.setState({
name: evt.target.value
});
}
});
How can I go about updating the Option states using the input from Name?
you need onChange function on the Name component as well, that sends the value to the parent component
Try this:
var Name = React.createClass({
onUpdate: function(evt) {
this.props.onChange(evt);
}
render: function(){
return(
<input type="text" onChange={this.onUpdate} value={this.props.value}/>
)
}
});
var Options = React.createClass({
getInitialState: function(){
return{
name: ''
}
},
render: function(){
return (
<div>
Name: <Name onChange={this.updateName} value={this.state.name} />
</div>
)
},
updateName: function(evt){
this.setState({
name: evt.target.value
});
}
});
Component Communication in React
Refer this link to know what are the ways to communicate between React components.
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.
My project keeps crashing when I attempt to insert a new recipe element. I use the this.state.recipes.map... in RecipeList to be able to update the recipes as needed(e.g. delete,edit,etc.). The delete functionality works, but I am unable to add a new recipe element.
If I switch the statement to this.props.recipes.map..., I am able to insert elements without an issue, but am unable to delete since the delete triggers a state change, and needs the state change to reflect the update instead of the props. Anyone have any tips on this issue? Thanks!
Recipe List:
var RecipeList = React.createClass({
getInitialState: function(){
return {recipes: []};
},
deleteRecipe: function(recipe){
var curRecipes = this.state.recipes.slice('');
curRecipes.splice(recipe.recipeKey,1);
this.setState({recipes: curRecipes});
},
componentWillMount: function(){
this.setState({recipes: this.props.recipes});
},
render: function(){
var recipeNodes = this.state.recipes.map(function(recipe,index){
return <Recipe onDelete={this.deleteRecipe} recipeKey={index} key={index} recipeTitle={recipe.recipeTitle} ingredients={recipe.ingredients} instructions={recipe.instructions} />
},this);
return(
<div>
{recipeNodes}
</div>
);
}
});
Recipe Container:
var RecipeBox = React.createClass({
getInitialState: function(){
return {showForm: false,
recipes: []
};
},
openForm: function(){
this.setState({showForm: true});
},
handleRecipeSubmit: function(recipe){
var curRecipes = this.state.recipes.slice('');
curRecipes.push({recipeTitle: recipe.recipeTitle,ingredients: recipe.ingredients, instructions: recipe.instructions});
this.setState({recipes: curRecipes});
},
render: function(){
return(
<div id="recipeBox">
<RecipeList recipes={this.state.recipes} />
<div className="recipeButtons">
<button id="addRecipeButton" className="btn-style" onClick={this.openForm}>Add Recipe</button>
</div>
{this.state.showForm ? this.refs.dialogWithCallBacks.show() : null}
<SkyLight
dialogStyles={formDialog}
ref="dialogWithCallBacks"
title="Add Recipe">
<RecipeForm onRecipeSubmit={this.handleRecipeSubmit} skylightRef={this.refs.dialogWithCallBacks} />
</SkyLight>
</div>
);
}
});
Recipe Form:
var RecipeForm = React.createClass({
getInitialState: function(){
return {hideDialog: false};
},
getFormData: function(){
var ingredients= document.getElementsByClassName("ingredient"),
recipeName = document.getElementsByName('recipeName')[0].value,
instructions = document.querySelector('textarea').value,
data = [];
ingredients = [].slice.call(ingredients).map(function(ingredient,index){
return {
"quantity": ingredient.childNodes[0].value,
"ingredient": ingredient.childNodes[1].value,
"unit": ingredient.childNodes[2].value
};
});
// Combine results into output array
data.push(recipeName);
data.push(ingredients);
data.push(instructions);
return data;
},
submitRecipe: function(event){
event.preventDefault();
var data = this.getFormData();
// Hide the SkyLight modal container
this.setState({hideDialog: true});
// Submit form
this.props.onRecipeSubmit({recipeTitle: data[0], ingredients: data[1], instructions: data[2]});
},
render: function(){
return(
<form onSubmit={this.submitRecipe}>
<section className="recipe-main">
<h2 style={{'border-bottom': 'none'}}>Recipe Name</h2>
<RecipeFormName />
<h2 style={{'border-bottom': 'none'}}>Ingredients</h2>
<RecipeFormIngredients />
</section>
<RecipeFormInstructions />
<input type="submit" value="Add Recipe" />
{this.state.hideDialog ? this.props.skylightRef.hide() : null}
</form>
)
}
});
You should move the code in componentWillMount to getInitialState.
getInitialState: function(){
return {recipes: this.props.recipes};
},
Needed to change the RecipeList component to
<RecipeList recipes={this.state.recipes} onChange={this.handleChange}/>
and then handle the deletion change from the RecipeBox instead of directly in RecipeList. Have to use this.props.map... to display new recipes and also delete visible ones.
var RecipeList = React.createClass({
getInitialState: function(){
return {recipes: this.props.recipes};
},
deleteRecipe: function(recipe){
var curRecipes = this.props.recipes.slice('');
curRecipes.splice(recipe.recipeKey,1);
this.props.onChange({recipes: curRecipes});
},
render: function(){
var recipeNodes = this.props.recipes.map(function(recipe,index){
return <Recipe onDelete={this.deleteRecipe} recipeKey={index} key={index} recipeName={recipe.recipeName} ingredients={recipe.ingredients} instructions={recipe.instructions} />
},this);
return(
<div>
{recipeNodes}
</div>
);
}
});
In my React.js to-do app, I'm trying to enable the return key to submit an item from my TextInput component to my ToDoList component. Right now the TextInput.inputSubmit method just console.logs the input value, but I'm wondering if I can have it trigger a prop (enter={that.addToDo}) inside of ToDoList. Or is there a better way?
JSFiddle
Edit: improved JSFiddle (courtesy of knowbody)
/** #jsx React.DOM */
var todos = [{text: "walk dog"}, {text: "feed fish"}, {text: "world domination"}, {text: "integrate return key"}];
var TextInput = React.createClass({
getInitialState: function() {
return {text: ''};
},
inputSubmit: function() {
//I think I want to trigger ToDoList's addToDo method from here?
console.log(this.refs.inputEl.getDOMNode().value);
this.setState({text: ''});
},
handleChange: function(evt) {
this.setState({text: evt.target.value});
},
handleKeyDown: function(evt) {
if (evt.keyCode === 13 ) {
return this.inputSubmit();
}
},
render: function() {
return (
<input value={this.state.text} ref="inputEl" onChange={this.handleChange} onKeyDown={this.handleKeyDown}/>
)
}
});
var SubmitButton = React.createClass({
render: function(){
return (
<button onClick={this.props.click}> Add </button>
)
}
});
var ToDo = React.createClass({
render: function(){
return (
<div>
<button onClick={this.props.click}>X</button>
<span> - {this.props.text}</span>
</div>
)
}
});
var ToDoList = React.createClass({
getInitialState: function (){
return {
todos: this.props.todos.splice(0)
}
},
deleteToDo: function(todo){
this.state.todos.splice(this.state.todos.indexOf(todo), 1);
this.setState({todos: this.state.todos});
},
addToDo: function(){
this.state.todos.push({text: this.refs.textIn.refs.inputEl.getDOMNode().value});
this.setState({
todos: this.state.todos
});
this.refs.textIn.setState({text: ''});
},
render: function(){
var that = this;
return (
<div>
{this.state.todos.map(function(todo) {
return (
<ToDo text={todo.text} click={that.deleteToDo.bind(null, todo)} />
)
})}
<br/>
<TextInput ref="textIn" enter={that.addToDo} />
<SubmitButton click={that.addToDo} />
</div>
)
}
});
React.renderComponent(<ToDoList todos={todos} />, document.body);
Your code is a bit messy but a quick fix will be to add:
this.props.enter(this.refs.inputEl.getDOMNode().value);
where your console.log() is. I will edit my answer with the full explanation once I'm on my laptop
I am learning ReactJS and trying to develop small CRUD form using it. At this point, I am trying to set input values from other sibling component to perform update.
So, if I click on edit button in first row on grid, Organzation Name and Description input boxes should get "abc com" and "company Abc" values respectively.
var OrganizationHandler = React.createClass(
render: function() {
return (
<div>
<OrganizationAPP onOrganizationSubmit=this.handleOrganizationSubmit} />
<OrganizationList external_DeleteOrganization={this.DeleteOrganizationFromServer} data= {this.state.data} />
</div>
);
}
});
var OrganizationList = React.createClass({
internal_DeleteOrganization: function(id) {
this.props.external_DeleteOrganization(id);
},
render: function() {
var results = this.props.data;
var parsed_results = results.objects;
var that = this;
var organizations = parsed_results.map(function(organization){
return <Organization onDeleteOrganization={that.internal_DeleteOrganization} id={organization.id} name={organization.name} description={organization.description} />
});
return(
<div>
{organizations}
</div>
);
}
});
var Organization = React.createClass({
handleDeleteClick: function() {
console.log(this.props);
this.props.onDeleteOrganization(this.props.id);
},
handleEditClick: function () {
alert(this.props.name);
},
render: function() {
return (
<div className="row">
<div className="small-2 large-2 columns">{this.props.id}</div>
<div className="small-4 large-4 columns">{this.props.name}</div>
<div className="small-4 large-4 columns"> this.props.description}</div>
<div className="small-2 large-2 columns">
<input type="button" onClick={this.handleDeleteClick} data-order={this.props.id} value="Delete" />
<input type="button" onClick={this.handleEditClick} data-order={this.props.id} value="Edit" />
</div>
</div>
);
}
});
var OrganizationAPP= React.createClass({
getInitialState: function() {
return {name: '', description:''};
},
onChangename: function(e) {
this.setState({name: e.target.value});
},
onChangedescription: function(e) {
this.setState({description: e.target.value});
},
handleSubmit: function() {
var name = this.refs.name.getDOMNode().value.trim();
var description = this.refs.description.getDOMNode().value.trim();
if (!description || !name) {
return false;
}
this.props.onOrganizationSubmit('{"name":"' + name +'", "description": "' + description +'"}');
this.refs.name.getDOMNode().value = '';
this.refs.name.getDOMNode().value = '';
this.setState({name: '', description: ''});
return false;
},
render: function() {
return (
<div>
<h1>Organization Setup:</h1>
<form onSubmit={this.handleSubmit}>
<div className="row">
<div className="small-12 large-3 columns">
<label>Organization Name:</label>
<input type="text" ref="name" required value={this.props.name} onChange={this.onChangename}/>
</div>
</div>
<div className="row">
<div className="small-12 large-7 columns">
<label>description:</label>
<input type="text" required ref="description" value={this.props.description} onChange={this.onChangedescription} />
</div>
</div>
<div className="row">
<div className="small-2 large-3 columns">
<button type="submit"> Add </button>
</div>
</div>
</form>
</div>
)
}
});
I have successfully performed addition and deletion operations but I don't know how can I set values to be edit in input boxes from sibling component. Please let me know if you don't understand anything as I just started learning reactjs and I know my code is not up to the mark.
Here is an example list editing app where editing an item content is done with a parent input (could easily be a sibling).
/** #jsx React.DOM */
//React is for rendering our app UI.
//Not for writing the app.
//So the first step is building out your logic in whatever you prefer.
//That could be Backbone, some other framework, or plain JS as we do here.
//our app
var app = {
//we'll tell this listener when we change
listener: null,
//a cheap way of creating IDs for our new data
nextId: 3,
//pre-populate our data
rows: [
{id: 1, name: "entry 1"},
{id: 2, name: "entry 2"}
],
//what data are we focused on
focusedId: 1,
//add a new row of data and set it to focused
addRow: function () {
var id = this.nextId++;
this.rows.push({id: id, name: ("entry " + id)});
this.setFocusedId(id);
},
//get the name property given the data id
getName: function(id){
return _.findWhere(this.rows, {id: id}).name;
},
//update the name property of the currently focused data
updateName: function (name) {
var id = this.focusedId;
_.findWhere(this.rows, {id: id}).name = name;
this.listener.changed();
},
//set the focused data
setFocusedId: function (id) {
this.focusedId = id;
this.listener.changed();
},
};
//a row component
var Row = React.createClass({
render: function () {
if (this.props.focused) {
return <span>Value: {this.props.name} [editing]</span>;
} else {
return <span>
Value: {this.props.name}
[<a href='#' onClick={this.props.focus}>edit</a>]
</span>;
}
}
});
//the main view
var View = React.createClass({
//our state is the app
getInitialState: function () {
return {
app: app
};
},
//before we render, start listening to the app for changes
componentWillMount: function () {
this.state.app.listener = this;
},
//update if the app tells us it changed
changed: function () {
this.forceUpdate();
},
//a handler we'll use for input fields
textChanged: function (event) {
this.state.app.updateName(event.target.value);
},
//let's render
render: function () {
var app = this.state.app;
//build an array of row components
var rows = _.map(app.rows, function (row) {
var focus = function () {
app.setFocusedId(row.id);
};
//the actual row component
//give the row a unique id
//give it a name, the focus handler function,
//and tell it if it has the current focus
return <li key={row.id}>
<Row
name={row.name}
focused={row.id == app.focusedId}
focus={focus}
/>
</li>;
});
//the main app view
return <div>
EDIT:
<input
type="text"
value={app.getName(app.focusedId)}
onChange={this.textChanged}
/>
<ul>{rows}</ul>
<a href="#"
onClick={function(){app.addRow()}}>
add row
</a>
</div>;
}
});
React.renderComponent(
<View />
, document.body);
Here is a working example:
http://jsbin.com/laxejufila/2/edit