I have created dynamically generate input text-fields but unable to find a way to read and get the values and stored it to an array. please find the code below
i have separate component for add new input field rows called IncrementTableRow
import React, {PropTypes} from 'react';
class IncrementTableRow extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<tr>
<th scope="row">{this.props.index}</th>
<td><input type="text" className="form-control" ref={"firstValue"+this.props.index} placeholder=""/></td>
<td><input type="text" className="form-control" ref={"secondValue"+this.props.index} placeholder=""/></td>
</tr>
);
}
}
export default IncrementTableRow;
also, i have main component to call IncrementTableRow below is the calling line.
export default class SuggestInterestProductDetails extends Component {
constructor(props) {
super(props);
this.state = {
rows: []
};
this.AddRow = this.AddRow.bind(this);
}
AddRow() {
this.setState({
rows: [{val: 5}, ...this.state.rows]
});
}
render() {
let rows = this.state.rows.map(row => {
return <Row />
});
return (
<div>
<button onClick={this.AddRow}>Add Row</button>
<table>
{rows}
</table>
</div>
);
}
}
i need to read each and every generated text field values and stored it to an array
your code example seems incomplete - you dont even add the values to your rows
so here only a short answer:
check react refs
https://facebook.github.io/react/docs/more-about-refs.html
you can add a ref to each row in your
let rows = this.state.rows.map(row => {
return <Row />
});
maybe an even better solution would be to add an onChange listener to your rows and update the state of your parrent component
let rows = this.state.rows.map((row,i) => {
return <Row ref={'row-'+i} onChange={(event) => this.myListener(event,i)} />
});
Related
I've come to a halt making this covid19 app where I can see a list of countries on the left side of the screen with the option of adding any number of countries to the right side of the screen, which displays more covid data of the added country. I'm also kinda new to React.
Problem is, when I click the add button the added state is updated, and it displays that added country on the right side of the screen. But, when I try adding another country I get an error. I believe the error is somewhere around when I try to setState({ state }) in the addCountry method from within App.js.
In other words, the 'added' state is only letting itself hold no more than one array element. Help much much much appreciated. I posted all the code.
index.js
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js
import CountryList from "./components/CountryList.js";
import Find from "./components/Find.js";
import Added from "./components/Added.js";
class App extends Component {
constructor() {
super();
this.state = {
countries: [],
inputbox: [],
added: [],
};
}
// Arrow functions capture "this" when they are defined, while standard functions do when they are executed.
// Thus, no need for the bind method. Awesome.
handleChange = (e) =>
this.setState({
inputbox: e.target.value,
});
getCountryData = async (slug) => {
const resp = await fetch(`https://api.covid19api.com/live/country/${slug}`);
var addedData = await resp.json();
// Api returns most days of covid, per country, that it tracks
// Thus, we want the last tracked day of a country
addedData = addedData[addedData.length - 1];
return addedData;
};
// Add a country to the added state
// Call when user clicks button associated with their desired country
addCountry = async (btnId) => {
const { countries, added } = this.state;
var addedData = await this.getCountryData(btnId);
countries.map((country) => {
// If the button ID is equal to the current country in the loops' Slug
if (btnId == country.Slug) {
try {
added.push([
{
addedCountry: addedData.Country,
confirmedTotal: addedData.Confirmed,
deathsTotal: addedData.Deaths,
recoveredTotal: addedData.Recovered,
activeTotal: addedData.Active,
},
]);
// (bug) IT IS PUSHING, BUT ITS NOT SETTING THE STATE!
// ITS ONLY LETTING ME KEEP ONE ITEM IN THE STATE
this.setState({ added });
console.log(added);
} catch (error) {
alert(`Sorry, country data not available for ${country.Country}`);
return;
}
}
});
};
removeCountry = (btnId) => {
const { added } = this.state;
added.map((added, index) => {
//console.log(added[index].addedCountry);
if (btnId == added[index].addedCountry) {
added.splice(index, 1);
this.setState({ added: added });
} else {
console.log("not removed");
return;
}
});
};
// Mount-on lifecycle method
async componentDidMount() {
const resp = await fetch("https://api.covid19api.com/countries");
const countries = await resp.json(); // parsed response
this.setState({ countries }); // set state to parsed response
}
render() {
// Filter out countries depending on what state the inputbox is in
const { countries, inputbox } = this.state;
const filtered = countries.filter((country) =>
country.Country.includes(inputbox)
);
return (
<div className="App Container">
<Find
placeholder="Type to find a country of interest..."
handleChange={this.handleChange}
/>
<div className="row">
<CountryList countries={filtered} addCountry={this.addCountry} />
<Added added={this.state.added} removeCountry={this.removeCountry} />
</div>
</div>
);
}
}
export default App;
Added.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import AddedCountry from "./AddedCountry.js";
class Added extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-md-6">
<Table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Country</th>
<th scope="col">Active</th>
<th scope="col">Confirmed Total</th>
<th scope="col">Recovered</th>
<th scope="col">Deaths</th>
<th scope="col">Action</th>
</tr>
</thead>
{this.props.added.map((added, index) => (
<AddedCountry
added={added[index]}
removeCountry={this.props.removeCountry}
/>
))}
</Table>
</div>
);
}
}
export default Added;
AddedCountry.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class AddedCountry extends Component {
constructor(props) {
super(props);
}
render() {
return (
<tbody>
<tr>
<td></td>
<td>{this.props.added.addedCountry}</td>
<td>{this.props.added.activeTotal}</td>
<td>{this.props.added.confirmedTotal}</td>
<td>{this.props.added.recoveredTotal}</td>
<td>{this.props.added.deathsTotal}</td>
<td>
{
<Button
onClick={() =>
this.props.removeCountry(
document.getElementById(this.props.added.addedCountry).id
)
}
id={this.props.added.addedCountry}
type="submit"
color="danger"
size="sm"
>
Remove
</Button>
}
</td>
</tr>
</tbody>
);
}
}
export default AddedCountry;
CountryList.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import Country from "./Country.js";
class CountryList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-md-6">
<Table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Country</th>
<th scope="col">Actions</th>
</tr>
</thead>
{
// Each country is a component
// Function will display all countries as the Map function loops through them
this.props.countries.map((country) => (
<Country countries={country} addCountry={this.props.addCountry} />
))
}
</Table>
</div>
);
}
}
export default CountryList;
Country.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class Country extends Component {
constructor(props) {
super(props);
}
render() {
return (
<tbody>
<tr>
<td></td>
<td>{this.props.countries.Country}</td>
<td>
{
<Button
onClick={() =>
this.props.addCountry(
document.getElementById(this.props.countries.Slug).id
)
}
id={this.props.countries.Slug}
type="submit"
color="success"
size="sm"
>
Add
</Button>
}
</td>
</tr>
</tbody>
);
}
}
export default Country;
Find.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class Find extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="Find container">
<br />
<Form>
<div className="form-row">
<div className="form-group col-md-6">
<h3>Find a Country</h3>
<Input
type="text"
className="form-control"
id="country"
placeholder={this.props.placeholder}
onChange={this.props.handleChange}
></Input>
</div>
</div>
</Form>
</div>
);
}
}
export default Find;
I haven't pored over all that code, but focusing right where you think the issue is it is obvious you are mutating your state object by pushing directly into the added array.
Solution
Don't mutate state!
Since it seems you only want to add a single new "add" and only when the button's btnId matches a country's slug, and the btnId can only ever be a valid value from the mapped countries array, I think this can be greatly simplified.
addCountry = async (btnId) => {
const addedData = await this.getCountryData(btnId);
if (addedData) {
this.setState(prevState => ({
added: prevState.added.concat({ // <-- concat creates a new array reference
addedCountry: addedData.Country,
confirmedTotal: addedData.Confirmed,
deathsTotal: addedData.Deaths,
recoveredTotal: addedData.Recovered,
activeTotal: addedData.Active,
}),
}));
} else {
alert(`Sorry, country data not available for ${country.Country}`);
}
};
Similarly the removeCountry handler is mis-using the array mapping function and mutating the added state. Array.prototype.filter is the idiomatic way to remove an element from an array and return the new array reference.
removeCountry = (btnId) => {
this.setState(prevState => ({
added: prevState.added.filter(el => el.addedCountry !== btnId),
}));
};
Additional Issues & Suggestions
Added.js
If you maintain the added array as a flat array (not an array of arrays) then it's trivial to map the values.
{this.props.added.map((added) => (
<AddedCountry
key={added}
added={added}
removeCountry={this.props.removeCountry}
/>
))}
Country.js & AddedCountry.js
I don't see any reason to query the DOM for the button id when you are literally right there and can enclose the country slug in the onClick callback.
<Button
onClick={() => this.props.addCountry(this.props.countries.Slug)}
id={this.props.countries.Slug}
type="submit"
color="success"
size="sm"
>
Add
</Button>
<Button
onClick={() => this.props.removeCountry(this.props.added.addedCountry)}
id={this.props.added.addedCountry}
type="submit"
color="danger"
size="sm"
>
Remove
</Button>
App.js
This may or may not matter, but it is often the case to do case-insensitive search/filtering of data. This is to ensure something like "France" still matching a user's search input of "france".
const filtered = countries.filter((country) =>
country.Country.toLowerCase().includes(inputbox.toLowerCase())
);
This is my first question here after years, so pardon me if I break any forum/platform rule.
I am trying to build a CGPA CALCULATOR so I am having an issue updating a variable on user input change.
I am a beginner so my code and description may be watery. The problem is with my handleChange method I guess, because every time I make an input (I am testing with the courseInput for now), the app crashes with the error:
TypeError: Cannot read property 'setState' of undefined
Someone should please explain to me in details.
I have actually tried a lot Googling but nothing seems wrong with my code.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
// this.courseInput = React.createRef();
this.state = {
courseInput: [],
courseCode: '',
courseUnit: [0],
courseGrade: [],
totalPoint: 0,
totalUnit: 0,
newCourseInput: <form>
<input onChange={this.handleChange} type="text" placeholder='COURSE CODE' value={this.courseCode} />
{/* <input type="number" placeholder='COURSE UNIT' ref={this.courseUnit} value={this.courseUnit} />
<input type="number" placeholder='COURSE GRADE' ref={this.courseGrade} value={this.courseGrade} /> */}
</form>
};
this.createAnother = this.createAnother.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// THIS createAnother TAKES THE CURRENT STATE OF courseInput AND CONCATENATES IT WITH THE newCourseInput TO MAKE LIST
createAnother() {
var courseInput = this.state.courseInput.concat(this.state.newCourseInput)
this.setState({ courseInput })
}
handleChange(event) {
var updatedCourseCode = event.target.value;
this.setState({ courseInput: updatedCourseCode }, () => console.log(this.state))
}
render() {
// console.log(this);
// var courseInput = this.state.courseInput;
return(
<div>
<header className="App-header">
<p>
THIS IS A CGPA CALCULATOR
</p>
</header>
{/* MAP FUNCTION LOOPS THROUGH THE ARRAY courseInput AND PRINTS OUT THE CODE UNIT AND GRADE IN IN ORDERED LIST */}
<ol>
{this.state.courseInput.map((courseInput, index) =>
<li key={index}>{courseInput}</li>
)}
</ol>
{/* THIS TRIGGERS AN EVENT HANDLER createAnother LOCATED UP THERE */}
<button onClick={this.createAnother} >ADD ANOTHER COURSE</button>
</div>
);
}
}
export default App;
You should not store jsx elements in your state, but only the necessary data to render these elements later when needed. you also have a mistakes(you tried to assign string to an courseInput whice is array).
import React, { Component } from "react";
// import './App.css';
class App extends Component {
constructor(props) {
super(props);
// this.courseInput = React.createRef();
this.state = {
courseInput: [],
courseCode: "",
courseUnit: [0],
courseGrade: [],
totalPoint: 0,
totalUnit: 0,
};
}
// THIS createAnother TAKES THE CURRENT STATE OF courseInput AND CONCATENATES IT WITH THE newCourseInput TO MAKE LIST
createAnother = () => {
var courseInput = this.state.courseInput.concat({
id: this.state.courseInput.length,
value: "",
});
this.setState({ courseInput });
};
handleCourseChange = (value, id) => {
const newCourseInputs = [...this.state.courseInput];
console.log(newCourseInputs);
console.log(value, id);
let courseToChange = newCourseInputs.find((c) => c.id == id);
courseToChange.value = value;
this.setState({ courseInput: newCourseInputs });
};
render() {
// console.log(this);
// var courseInput = this.state.courseInput;
console.log(this.state.courseInput);
return (
<div>
<header className="App-header">
<p>THIS IS A CGPA CALCULATOR</p>
</header>
{/* MAP FUNCTION LOOPS THROUGH THE ARRAY courseInput AND PRINTS OUT THE CODE UNIT AND GRADE IN IN ORDERED LIST */}
<ol>
{this.state.courseInput.map((courseInput, index) => (
<li key={index}>
<input
onChange={(e) =>
this.handleCourseChange(e.target.value, courseInput.id)
}
type="text"
placeholder="COURSE CODE"
value={courseInput.value}
/>
</li>
))}
</ol>
{/* THIS TRIGGERS AN EVENT HANDLER createAnother LOCATED UP THERE */}
<button onClick={this.createAnother}>ADD ANOTHER COURSE</button>
</div>
);
}
}
export default App;
this code will probably work as you intended.
in handleChange use arrow function instead of regular function :
class A {
handleChange(event){
this // the keyword "this" refer to the function handleChange
}
}
class A {
handleChange =(event)=>{
this // the keyword "this" refer to the class A
}
}
The Difference Between Regular Functions and Arrow Functions : read-me
You're not binding this to handleChange correctly in input tag at
<input onChange={this.handleChange} type="text" placeholder='COURSE CODE' value={this.courseCode} />
You should update onChange function to onChange={this.handleChange.bind(this)}
this is actually a binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called, not where it is declared. More at https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/this%20%26%20object%20prototypes/ch2.md
I have a main Table component that maintains the table's state. I have a dumb component which gets props from the main component. I use it to render the table row layout. I am trying to make this table editable. For this reason, I need a way to find out which tr was edited. Is there a way to get access to the tr key using which I can get access to the whole object?
No you can't get the value of a key in a child prop. From the docs:
Keys serve as a hint to React but they don’t get passed to your
components. If you need the same value in your component, pass it
explicitly as a prop with a different name
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
A possible solution right of my head might be the following:
import React from 'react';
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [
{
id: 0,
title: "ABC"
},
{
id: 1,
title: "DEF"
},
{
id: 2,
title: "GHI"
}
]
}
}
render() {
return <table>
<tbody>
{
this.state.rows.map((item) => <Row key={item.id} item={item} updateItem={this.updateItem} />)
}
</tbody>
</table>
}
updateItem = (newItemData) => {
const index = this.state.rows.findIndex((r) => r.id == newItemData.id);
let updatedRows = this.state.rows;
updatedRows.splice(index, 1, newItemData);
this.setState({
rows: updatedRows
});
}
}
const Row = ({item, updateItem}) => {
const [title, setValue] = React.useState(item.title);
return <tr>
<td>{item.id}</td>
<td>
<input type="text" value={title} onChange={(e) => setValue(e.currentTarget.value)} />
</td>
<td>
<button onClick={() => updateItem({...item, title})}>Save</button>
</td>
</tr>
};
If you want to send from nested component to parent some data use a callback function
How can I create the method when I click on one checkbox other checkbox unselceted and just can select one of them.
import React, { Component } from 'react';
export default class Tablerow extends Component {
constructor(props){
super(props);
let {listwebsites} = this.props;
listwebsites.checked = false;
this.state = {
fields : {
id: listwebsites.id
}
}
this.click = this.click.bind(this);
this.selectOnlyThis = this.selectOnlyThis.bind(this);
}
click(value){
this.props.handleChangess(this.state, value);
};
render() {
const {listwebsites} = this.props;
return (
<tr>
<td><input id={`checkbox_${listwebsites.id}`} value={listwebsites.checked} onChange={e => this.click(e.target.checked)} type="checkbox" name="record"/></td>
<td>{listwebsites.name}</td>
<td>{listwebsites.url}</td>
</tr>
)
}
}
Here's how you do it, in TableRow's parent which is App.js in this snippet, use selectedId state which store the id or TableRow's listwebsite's id if checked and will be null if not checked.
Inside your TableRow render, use disabled attr in your<input/> element. The disabled will check the selectedId props passed down from <App/>, if selectedId not null and selectedId value !== current <TableRow/> listwebsite's id, then disable the <input/>.
const listwebsitesData = [
{ id: 1, name: 'name-1', url: 'Url-1' },
{ id: 2, name: 'name-2', url: 'Url-2' },
{ id: 3, name: 'name-3', url: 'Url-3' }
]
class App extends React.Component {
constructor(props){
super(props);
this.state = {
selectedId: null,
}
this.handleChangess = this.handleChangess.bind(this);
}
handleChangess(id, value) {
this.setState({selectedId: value===true?id:null})
}
render(){
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{
listwebsitesData.map((data)=>{
return <Tablerow selectedId={this.state.selectedId} listwebsites={data} handleChangess={this.handleChangess} />
})
}
</div>
)
}
}
class Tablerow extends React.Component {
constructor(props) {
super(props);
let { listwebsites } = this.props;
listwebsites.checked = false;
this.state = {
fields: {
id: listwebsites.id
}
}
this.click = this.click.bind(this);
this.selectOnlyThis = this.selectOnlyThis.bind(this);
}
click(value) {
this.props.handleChangess(this.state.fields.id, value);
};
selectOnlyThis(){
}
render() {
const { listwebsites, selectedId } = this.props;
return (
<tr>
<td><input disabled={selectedId && selectedId!==listwebsites.id} id={`checkbox_${listwebsites.id}`} value={listwebsites.checked} onChange={e => this.click(e.target.checked)} type="checkbox" name="record" /></td>
<td>{listwebsites.name}</td>
<td>{listwebsites.url}</td>
</tr>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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>
<div id="root"></div>
Basically, move the selection state one level up. You must be looping over Table row, in a Table component one level up. Put a state field there, say, 'selectedId', and pass it as prop to all the Table row components. Later, onChange will propagate from Table row with 'id' to Table onChangeHandler, for 'selectedId' update. In the render function for Table row, simply add say checked = id === selectedId thus, making only one of the Table rows selected at any given time. To make it more generic, you can later add say 'multiple' true/ false flag, where the component can switch between allowing multiple vs single checkbox selection.
Working example
https://codesandbox.io/s/zn9l7qpn83
By default, first one would be selected. As you select another one, it would deselect the other one; thus allowing only one to be selected at any given time.
Hope it helps!
You should add "checked={}" on checkbox and return true for the time you want it checked.
<input checked={selectedId && selectedId!==listwebsites.id} id={`checkbox_${listwebsites.id}`} value={listwebsites.checked} onChange={e => this.click(e.target.checked)} type="checkbox" name="record" />
This will check your checkbox when only this condition (selectedId && selectedId!==listwebsites.id)
I have a form component that has a state containing an array of items.
I am having a hard time trying to update the state of the form when one of the item inputs gets updated.
At first I was creating a state on the items themselves and updating the values using the following code:
class ItemRow extends Component{
constructor(props){
super(props)
this.state = this.props.item;
}
updateItem(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
.....
render(){
return (
<FormControl
type="text"
name="name"
value={this.state.name}
onChange={this.updateItem}
/>
<FormControl
type="text"
name="price"
value={this.state.price}
onChange={this.updateItem}
/>
.....
)
}
}
This worked fine for updating the value of the of the inputs, however the state was local to the item and not reflected or accessible by the form
I am trying to figure out how to keep the state in the form and have the item update the state of the form
I think this is the right approach but I can't figure out how to get it to work.
At this point I have something similar the following:
class Form extends Component{
this.state = {
items: [
{ name: 'soup', price: 7, quantity: 1 }
{ name: 'salad', price: 5, quantity: 2 }
]
}
updateItem(e) {
// Not sure how to handle updating
}
removeItem(item) {
let items = this.state.items;
items.splice(items.indexOf(item), 1);
this.setState({items: items})
}
render(){
return(
<ItemTable items={this.state.items} updateItem={this.updateItem} removeItem={this.removeItem} />
)
}
}
ItemTable:
class ItemTable extends Component {
removeItem(item){
this.props.removeItem(item)
}
render(){
let items = [];
this.props.items.forEach((item) => {
items.push(<ItemRow item={item} key={item.id} removeItem={this.removeItem.bind(this,item)} updateItem={this.props.updateItem}/>);
});
return(
{items}
)
}
}
ItemRow:
class ItemRow extends Component {
removeItem(item){
this.props.removeItem(item)
}
render() {
return (
<FormControl
type="text"
name="name"
value={this.props.item.name}
onChange={this.updateItem}
/>
<FormControl
type="text"
name="quantity"
value={this.props.item.quantity}
onChange={this.updateItem}
/>
<FormControl
type="text"
name="price"
value={this.props.item.price}
onChange={this.updateItem}
/>
<Button bsStyle="warning" onClick={this.removeItem}><Glyphicon glyph="trash"/></Button>
)
}
}
You're very close to the solution.
If you need to have a state shared between components, you should have it in the most parent component that should be aware of the state (in your case the Form component).
You pass down as props the method "updateItem" from the Form to the ItemTable and then ItemRow (like you're doing)
At this stage, inside the ItemRow you can use the method by calling 'this.props.updateItem' and you can run the function defined in Form, passing some parameters, if you need to.