The issue I am having is after entering text for the first time, I then click Add and the input text box gets cleared. However when I start to enter text in again, the input text will not let me enter any text apart from the first letter. I'm not sure why it's doing this.
var FormTextBox = React.createClass({
handleOnBlur: function (e) {
this.props.onBlur(e.target.value);
},
render: function () {
return (
<input value={this.props.value} key={this.props.fid} type="text" onBlur={this.handleOnBlur} />
)
}
});
var TestFormTextBox = React.createClass({
getInitialState: function (e) {
return {
value: ''
}
},
handleOnAdd: function (e) {
this.setState({ value: '' });
},
handleTextInfo: function (value) {
this.setState({ value: value });
},
render: function () {
return (
<div>
<table>
<tbody>
<tr>
<td>Details</td>
<td></td>
</tr>
<tr>
<td><FormTextBox value={this.state.value} fid={1} onBlur={this.handleTextInfo} /></td>
<td><button onClick={this.handleOnAdd}>Add</button></td>
</tr>
</tbody>
</table>
</div>
)
}
});
I am surprised that this even works the first time. With controlled components in react (ones where you are setting the value like you are with your input). you need to update the value whenever the user changes the text (with the onChange() event).
I made a JS fiddle here with your original code and you can see you can't even update the value in the input. In order to get it to update you need to replace the onBlur event with an onChange event like this JS fiddle. Hope that helps!
As mentioned here (https://facebook.github.io/react/docs/forms.html#controlled-components),
A controlled has a value prop. Rendering a controlled will reflect the value of the value prop.
User input will have no effect on the rendered element because React has declared the value to be Hello!. To update the value in response to user input, you could use the onChange event
You need to either change onBlur to onChange, or use defaultValue instead of value. e.g.
<input defaultValue={this.props.value} key={this.props.fid} type="text" onBlur={this.handleOnBlur} />
You need to change the value of the state variable as soon as you are typing the input value because that is what you are providing the input if you don't change it then the input wont show the updated value. In order to do this you need to use the onChange event everywhere.
var FormTextBox = React.createClass({
handleOnChange: function (e) {
this.props.onChange(e.target.value);
},
render: function () {
return (
<input value={this.props.value} key={this.props.fid} type="text" onChange={this.handleOnChange} />
)
}
});
var TestFormTextBox = React.createClass({
getInitialState: function (e) {
return {
value: ''
}
},
handleOnAdd: function (e) {
this.setState({ value: '' });
},
handleTextInfo: function (value) {
this.setState({ value: value });
},
render: function () {
return (
<div>
<table>
<tbody>
<tr>
<td>Details</td>
<td></td>
</tr>
<tr>
<td><FormTextBox value={this.state.value} fid={1} onChange={this.handleTextInfo} /></td>
<td><button onClick={this.handleOnAdd}>Add</button></td>
</tr>
</tbody>
</table>
</div>
)
}
});
ReactDOM.render(<TestFormTextBox />, document.getElementById('app'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.min.js"></script>
<div id="app"></div>
Related
I have an application what takes the date from inputs and saving it, the data are apendded bellow, something like todo list. But now i have an issue trying to display the data, because i want to display it in a specific order as table row, but now the data is not showing properly, because i want to have Name under Name, Old under old, delete button under delete Text, ad edit button under edit text.
How to do this?
link to my application: https://codesandbox.io/s/nifty-moore-g86kd
There are a few issues in your code.
You don't have any state to keep track of the added users
On a form submit instead of updating the data you're trying to directly update the DOM with submitted data. Which is not the right way to do things in react.
import React, { useState, useEffect } from "react";
export default function App() {
const [user, setUser] = useState({
name: "",
old: ""
});
// A new state to keep track of the added users
const [users, setUsers] = useState([]);
const changeUser = e => {
const v = e.target.value;
setUser({
...user,
[e.target.name]: v
});
};
// On form submit, all you need to do is to update the users state
// Then render will take care of the rest
const submitForm = e => {
e.preventDefault();
setUsers([...users, user]);
};
// This is how in react we update the content
// Whenever, there is a change in state, this will get called and content will be updated
// Ps: It's being called in the render
const renderBody = () => {
const content = [];
users.map(item => {
content.push(
<tr>
<td>{item.name}</td>
<td>{item.old}</td>
<td>Delete btn</td>
<td>Edit btn</td>
</tr>
);
});
return content;
};
return (
<div className="to-do">
<form action="" onSubmit={submitForm}>
<label htmlFor="">
Name
<input
name="name"
onChange={changeUser}
value={user.name}
type="text"
/>
</label>
<label htmlFor="yes">
Old Yes
<input
id="yes"
name="old"
onChange={changeUser}
value="yes"
type="radio"
/>
</label>
<label htmlFor="no">
Old No
<input
id="no"
name="old"
onChange={changeUser}
value="no"
type="radio"
/>
</label>
<input value={user.old} type="submit" value="SUBMIT" />
</form>
<div className="res">
<table>
<tr>
<th>Name</th>
<th>OLD</th>
<th>DELETE</th>
<th>Edit</th>
</tr>
<tr id="res" />
{renderBody()}
</table>
</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
So what you need
State for users to keep track of the added users
On form submit, a trigger to update that users state
A loop, to iterate over users array and return table rows with content
Let's start off with the problem I'm having and telling you guys what I would like to achieve.
First of all, I'm getting this error
Warning: A component is changing a controlled input of type text to be uncontrolled.
Input elements should not switch from controlled to uncontrolled (or vice versa).
Decide between using a controlled or uncontrolled input element for the lifetime of the component.
My goal is to save data first to the state. Or I should use List, or dictionary? This is where I'm stuck. I will post my code here also for you to check what I'm doing wrong or what should I do differently.
import React from 'react'
import './TableData.css'
class TableData extends React.Component{
constructor(props){
super(props)
this.state = {
rows:[{service: '',
quantity: '',
price: '',
sum: ''}]
}
this.handleChange = this.handleChange.bind(this)
this.handleAddRow = this.handleAddRow.bind(this)
this.handleRemoveRow = this.handleRemoveRow.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange = idx => event => {
var rows = [...this.state.rows]
rows[idx] = {
[event.target.name]: event.target.value
}
this.setState({
rows
})
var data = this.state.rows
console.log("Log me", data)
}
handleAddRow = () => {
var item = {
service: '',
quantity: '',
price: '',
sum: ''
}
this.setState({
rows: [...this.state.rows, item]
})
}
handleRemoveRow = () => {
this.setState({
rows: this.state.rows.slice(0, -1)
})
}
handleSubmit = (event) => {
event.preventDefault()
var tableData = this.state.rows
console.log("Final data is:", tableData)
}
render() {
return (
<div className="tablePos container" >
<form onSubmit={this.handleSubmit}>
<div className="row">
<table id="tab_logic">
<thead className="tableBackground">
<tr>
<th className="col-md-auto"> Service </th>
<th className="col col-lg-2"> Quantity </th>
<th className="col col-lg-2"> Price </th>
<th className="col col-lg-2"> Sum </th>
</tr>
</thead>
<tbody>
{this.state.rows.map((item, idx) => (
<tr key={idx}>
<td>
<input className="form-control" type="text" name="service" placeholder="Ex: Cloud Service" value={this.state.rows[idx].service} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="quantity" placeholder="Ex: 2 Month" value={this.state.rows[idx].quantity} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="price" placeholder="Ex: 75.00" value={this.state.rows[idx].price} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="sum" placeholder="Ex: 150.00" value={this.state.rows[idx].sum} onChange={this.handleChange(idx)} />
</td>
</tr>
))}
</tbody>
</table>
</div>
<button>Send Data!</button>
</form>
<button onClick={this.handleAddRow} className="btn btn-success">Add Row</button>
<button onClick={this.handleRemoveRow} className="btn btn-danger">Delete Last Row</button>
</div>
);
}
}
export default TableData
So basically it creates 4 input boxes and then you can write in and if you are done you click Send Data it saves it to state or add new row and then it will add new row for you to input data. What I do get is the following from that code.
Console log picture of the data
It only saves the last input field data when I click send data not all of them.
Sorry about my messy explanation but I hope you did understand my problem and thank you for your replies!
while assigning the values inside onChange. You are spreading the array as needed. But you have to spread the object too.. Otherwise, it will just assign the last-changed-input-field-value to the object.
rows[idx] = {
...this.state.rows[idx],
[event.target.name]: event.target.value
};
You can find my code below.
https://codesandbox.io/s/small-dew-wjqqi
I contrusted a table-form with two children node buttons and rows using Reactjs.
After change the value of each child node, the parent node receive the new state,
but no re-render happened, why?
<!DOCTYPE html>
<html>
<head>
<title> w3du </title><meta charset="utf-8">
<!-- Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
<style>
.quarcol {
width:25%
}
.vmidcol {
vertical-align:middle
}
.cyan {
color:#fff;
background-color:#63aae7
}
.blue {
color:#fff;
background-color:#60a0e0
}
</style>
</head>
<body>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title text-center">Panel</h3>
</div>
<div id="canvas"></div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>
<script src="https://cdn.bootcss.com/react/15.6.1/react.js"></script>
<script src="https://cdn.bootcss.com/react/15.6.1/react-dom.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.26.0/babel.js"></script>
<script src="https://cdn.bootcss.com/js-signals/1.0.0/js-signals.js"></script>
<script type="text/babel">
The 'table-row' stuff, the 3rd column is number, after modified, the associated button doesn't changed its value, vice versa
var BudgetTableRow = React.createClass({
getInitialState : function(){
return {
n : this.props.n,
price : this.props.price
};
},
handleChangeNum : function(e){
this.setState({
n : e.target.value
});
this.props.callback(this.props.idx, e.target.value);
},
render : function(){
var styfull = {
width : '100%',
height : '30%',
};
var stycenter = {
verticalAlign : 'middle',
horizontalAlign : 'middle'
};
return (
<tr className={this.props.color}>
<td className="quarcol text-center monofont" style={stycenter}>
<a href="#" data-toggle="tooltip" title={"<h1>"+this.props.label+"</h1>"}>{this.props.name}</a>
</td>
<td className="quarcol"><input name={this.props.label+'-price'}
className="form-control text-center monofont"
type="text" defaultValue={this.state.price} style={styfull}
onChange={this.handleChangePrice}
/></td>
<td className="quarcol"><input name={this.props.label+'-n'}
className="form-control text-center monofont"
type="text" defaultValue={this.state.n} style={styfull}
onChange={this.handleChangeNum} onFocus={this.handleFocus} onBlur={this.handleBlur}
/></td>
<td className="quarcol text-center monofont" style={stycenter}>
{this.state.price * this.state.n}
</td>
</tr>
);
}
});
The 'button' stuff, after modified, the associated table-row doesn't changed its value
var BudgetTableButton = React.createClass({
getInitialState : function(){
return {
n : this.props.n
};
},
handelClick : function(e){
e.preventDefault();
var n = (this.state.n=="0" || this.state.n=="") ? "1" : "0";
this.setState({
n : n
});
this.props.callback(this.props.idx, n);
},
render : function(){
var cls = (this.state.n=="0" || this.state.n=="") ? null : 'btn-info';
return (
<button className={'btn ' + cls} onClick={this.handelClick}>{this.props.label} {this.state.n}</button>
);
}
});
The parent node, the console.log() does right, but the form doesn't re-render, i used unique-key, is it something wrong with this.state.items.map ?
var BudgetTable = React.createClass({
getInitialState : function(){
return {
items : this.props.items
};
},
handleCallback : function(idx, n){
var items = this.state.items;
items[idx].n = n;
this.setState({
items : items
});
},
render : function(){
console.log(this.state.items[0]);
return (
<table className="table monofont">
<thead>
<tr className="blue">
<td className="text-center" colSpan="4">Table</td>
</tr>
</thead>
<tbody>
<tr><td colSpan="4">
{this.state.items.map((it, idx) =>
<BudgetTableButton key={it.label+'r'} label={it.label} idx={idx} callback={this.handleCallback} n={it.n} />
)}
</td></tr>
{this.state.items.map((it, idx) =>
<BudgetTableRow key={it.label+'b'} label={it.label} name={it.name} price={it.price} idx={idx} callback={this.handleCallback} n={it.n} />
)}
</tbody>
</table>
);
}
});
function init(){
return [
{
"price": 1340,
"name": "shoe",
"label": "Q41",
"n" : 1
}, {
"price": 1290,
"name": "clothes",
"label": "Q42",
"n" : 1
}
];
}
ReactDOM.render(
<BudgetTable items={init()} />,
document.getElementById("canvas")
);
</script>
</body>
</html>
The problem in your example is that you are using two states.
(I will talk here about the button component, but both cases are the same)
As you probably already know, the local state can only trigger update on the component it belongs to.
You are trying to sync the button's state with the state of the main app. However getInitialState is only called on initial render and not on re-render (info here).
In order to do what you want, you will need to get the button/row information from the props and not from the state (you already have them in props).
Furthermore, I would delete the children's state at all, because you don't need it. It is better that you keep the state in the top level component and only pass props and functions (like the callback fn) down to the children.
So in this case, if you change the button to the following code, it works flawlessly:
var BudgetTableButton = React.createClass({
handelClick : function(e){
e.preventDefault();
var n = this.props.n === "0" || this.props.n === "" ? "1" : "0";
this.props.callback(this.props.idx, n);
},
render : function(){
var cls = (this.props.n=="0" || this.props.n=="") ? null : 'btn-info';
return (
<button className={'btn ' + cls} onClick={this.handelClick}>{this.props.label} {this.props.n}</button>
);
}
});
You are not mutating the state.
handleCallback : function(idx, n){
var items = [...this.state.items];
items[idx].n = n;
this.setState({
items : items
});
}
I'm new at React, I have 3 years experience with Angular, which is why React seems strange to me. I created many input like this:
<tr>
<td>
<input value={this.state.x}
onChange={this.handleChange}/>
</td>
<td>
<input value={this.state.y}
onChange={this.handleChange}/>
</td>
<td>
<input value={this.state.z}
onChange={this.handleChange}/>
</td>
</tr>
from what I learn so far, I had to handle this input change event one by one for each <input> which I find laborious. Can I write one function to update all the input above ? Like:
handleChange = (event) => {
let obj = {};
obj[key] = event.target.value; // the key is my variable name, eg: x, y, z
this.setState(obj)
}
Does it possible to give several input a single handler function ? Thanks in advance
You can use event.target.name and event.target.value in order to update your component's as long as you set name property in each input:
this.setState({
[event.target.name]: event.target.value,
})
class App extends React.Component {
constructor() {
super()
this.state = {
x: '',
y: '',
z: '',
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
})
}
render() {
return (
<div>
<table>
<tr>
<td>
<input name="x" value={this.state.x} onChange={this.handleChange} />
</td>
<td>
<input name="y" value={this.state.y} onChange={this.handleChange} />
</td>
<td>
<input name="z" value={this.state.z} onChange={this.handleChange} />
</td>
</tr>
</table>
<div>Current state: {JSON.stringify(this.state)}</div>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
One other way to achieve this without giving names to your inputs is to bind names of the properties in your state as arguments in your change handlers. Then in the event handler you will receive the name of the property as the first argument and the event as the second argument. Here is an example:
class Test extends React.Component {
state = {
x: '',
y: '',
z: ''
}
handleChange(name, event) {
this.setState({ [name]: event.target.value });
}
render() {
return (<table>
<tr>
<td>
<input value={this.state.x}
onChange={this.handleChange.bind(this, 'x')} />
</td>
<td>
<input value={this.state.y}
onChange={this.handleChange.bind(this, 'y')} />
</td>
<td>
<input value={this.state.z}
onChange={this.handleChange.bind(this, 'z')} />
</td>
</tr>
</table>);
}
}
This way is probably less pretty than giving names to your inputs, but it works if you for some reason don't want to have names on the inputs.
handleChange = () => {
const {value, name} = event.target
this.setState({
[name] : value
})
}
In the below example, I have a simple <table> with a checkbox inside it. I have click events on the td, tr and checkbox. I want to be able to click the checkbox and stop the bubbling to the td and tr. A simple "event.stopPropagation()" works great.
The problem is that if I want to connect a <label> to the checkbox using "htmlFor", the events won't stop bubbling when the label is clicked (even though it still works as expected with the checkbox itself is clicked). And even more strangely, the bubbling seems to happen in a weird order (as in Checkbox click is received last!).
Here's the code:
var Hello = React.createClass({
func1(e){
console.log('tr was clicked')
},
func2(e){
console.log('td was clicked')
},
func3(e){
e.stopPropagation();
console.log('Checkbox was clicked')
},
render: function() {
return <table>
<tbody>
<tr onClick={this.func1}>
<td onClick={this.func2}>
<input id="thing" type="checkbox" onClick={this.func3} />
<label htmlFor="thing"> label for checkbox</label>
</td>
</tr>
</tbody>
</table>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
...And here's the Fiddle:
https://jsfiddle.net/69z2wepo/52785/
(View the console for the click events)
The label doesn't have a click handler of it's own, and can't stop propagation, so when you click the label normal event bubbling takes place. This means that all the parent's event handlers are invoked in the correct order. In addition, because of the htmlFor, the checkbox click handler is also triggered, but not as part of the event bubbling.
To solve the problem, add a separate click handler to the label that only includes .stopPropgation() (demo):
var Hello = React.createClass({
func1(e){
console.log('tr was clicked')
},
func2(e){
console.log('td was clicked')
},
func3(e){
e.stopPropagation();
console.log('Checkbox was clicked')
},
stopLabelPropagation(e) {
e.stopPropagation();
},
render: function() {
return <table>
<tbody>
<tr onClick={this.func1}>
<td onClick={this.func2}>
<input id="thing" type="checkbox" onClick={this.func3} />
<label htmlFor="thing" onClick={ this.stopLabelPropagation }>label for checkbox</label>
</td>
</tr>
</tbody>
</table>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
Try wrapping with a span and add evt.stopPropagation() to span's onClick
<span onClick={evt => evt.stopPropagation()}>
<input id="thing" type="checkbox" onClick={this.func3} />
<label htmlFor="thing"> label for checkbox</label>
</span>
Just adding an onClick on the label, with the same function binding is helping. Here's the code and the relevant JSBin: https://jsbin.com/caneqi/2/edit?html,js,output
var Hello = React.createClass({
func1(e){
console.log('tr was clicked')
},
func2(e){
console.log('td was clicked')
},
func3(e){
e.stopPropagation();
console.log('Checkbox was clicked')
},
render: function() {
return <table>
<tbody>
<tr onClick={this.func1}>
<td onClick={this.func2}>
<input id="thing" type="checkbox" onClick={this.func3} />
<label htmlFor="thing" onClick={this.func3}> label for checkbox</label>
</td>
</tr>
</tbody>
</table>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);