React Trying to edit input but got an error - javascript

I'm working on the edit/update the prices in the price list in my React app. I'm almost close but was unable to input any changes. Every time I tried to change the price in the input, I get an error saying "TypeError: onChange is not a function"...
I'm trying to write my code that's almost similar to this tutorial: https://medium.com/the-andela-way/handling-user-input-in-react-crud-1396e51a70bf#8858
So far, I was able to toggle the price between input field and back but I'm trying to edit the input to save any changes...What am I missing? I kept checking between my code and this tutorial to make sure everything is working...
Here's my code (functions) in the Parent component, PriceForm.js:
toggleItemEditing = index => {
this.setState({
priceArr: this.state.priceArr.map((item, itemIndex) => {
if (itemIndex === index) {
return {
...item,
isEditing: !item.isEditing
}
}
return item;
})
});
};
handlePriceUpdate = (event, index) => {
const target = event.target;
const value = target.value;
const number = target.number;
this.setState({
priceArr: this.state.priceArr.map((item, itemIndex) => {
if (itemIndex === index) {
return {
...item,
[number]: value
}
}
return item;
})
});
console.log("price update", event);
};
and where it's called in:
{this.state.priceArr.map((props, index) => (
<PriceBox
{...props}
key={props.date}
toggleEditing={this.toggleItemEditing}
handleDeletePrice={this.handleDeletePrice}
onChange={this.handlePriceUpdate}
/>
))}
And here's my code for the Child component, SinglePriceBox.js:
import React, { Component } from "react";
export default class SinglePricebox extends Component {
constructor(props) {
super(props);
this.state = {
isInEditMode: false,
todaydate: this.props.date
};
this.toggleEditPriceSubmission = this.toggleEditPriceSubmission.bind(this);
}
toggleEditPriceSubmission() {
this.setState(state => ({ isInEditMode: !state.isInEditMode }));
}
render() {
const { isInEditMode, onChange, index } = this.state;
return (
<div className="pricebox">
<article className="pricetable">
<table>
<tbody>
<tr>
<td className="date-width">{this.props.date}</td>
<td className="price-width">
{isInEditMode ? (
<input type="text" name="number" value={this.props.number} onChange={event => onChange(event, index)} />
) : (
this.props.number
)}
</td>
<td className="editing-btn">
<button
type="button"
className="edit-btn"
onClick={this.toggleEditPriceSubmission}
>
{isInEditMode ? "Save" : "Edit"}
</button>
</td>
<td>
{this.props.handleDeletePrice && (
<button
type="button"
className="delete-btn"
onClick={() => this.props.handleDeletePrice(this.props.date)}
>
X
</button>
)}
</td>
</tr>
</tbody>
</table>
</article>
</div>
);
}
}
You can check out my demo at https://codesandbox.io/s/github/kikidesignnet/caissa. You will be able to check out the error if you click on Prices button, then click on Edit button to change the price in the input field that appears.

import React, { Component } from "react";
export default class SinglePricebox extends Component {
constructor(props) {
super(props);
this.state = {
isInEditMode: false,
todaydate: this.props.date
};
this.toggleEditPriceSubmission = this.toggleEditPriceSubmission.bind(this);
this.handleChange = this.handleChange.bind(this);
}
toggleEditPriceSubmission() {
this.setState(state => ({ isInEditMode: !state.isInEditMode }));
}
handleChange = (e, index) => {
// write your code here
}
render() {
const { isInEditMode, index } = this.state;
return (
<div className="pricebox">
<article className="pricetable">
<table>
<tbody>
<tr>
<td className="date-width">{this.props.date}</td>
<td className="price-width">
{isInEditMode ? (
<input type="text" name="number" value={this.props.number} onChange={event => this.handleChange(event, index)} />
) : (
this.props.number
)}
</td>
<td className="editing-btn">
<button
type="button"
className="edit-btn"
onClick={this.toggleEditPriceSubmission}
>
{isInEditMode ? "Save" : "Edit"}
</button>
</td>
<td>
{this.props.handleDeletePrice && (
<button
type="button"
className="delete-btn"
onClick={() => this.props.handleDeletePrice(this.props.date)}
>
X
</button>
)}
</td>
</tr>
</tbody>
</table>
</article>
</div>
);
}
}

In the following line:
<input type="text" name="number" value={this.props.number} onChange={event => onChange(event, index)} />
You're calling this.state.onChange but there is no onChange in your state:
this.state = {
isInEditMode: false,
todaydate: this.props.date
};
After looking at your codesandbox, it seems that onChange is passed as a props to PriceBox, so you should do this in SinglePriceBox render():
const { isInEditMode, index } = this.state;
const { onChange } = this.props;
This will remove the error you were having, but the update still doesn't work because target.number is undefined in PriceForm.handlePriceUpdate :(
However target.name is defined and equal to 'number' which is a valid key in your price list

the problem is in how you pass your function as a prop. You should pass the function call in this way:
<PriceBox
{...props}
[...]
onChange={(e) => this.handlePriceUpdate(e, index)}
/>
Then call it in your child component:
<input
type="text"
[...]
onChange={event => this.props.onChange(event, index)}
/>
Also, I would not using index here, I would rather use an ID from your object instead

Related

Hold input values in local state, but pass to parent to submit form

I am new to React and learning by building a simple timesheet application that users can fill out and when it submits, it posts the data to a SharePoint list using a SharePoint API (see below).
How I want it to work
The tool has been created using a table and each row is a table row stored in a child component. When a user clicks the 'add a task' button this creates a new table row, where they can add the time they have completed for that individual task. They can add as many items as they need, then when they hit the 'submit' button, this submits each individual task to the Sharepoint list as separate items.
My Issue
At the moment when a user adds a task, then adds text to the input field. It is passed up to the parent state, where the data will need to be submitted, but this means that when you enter text into one of the rows, it is reflected in ALL rows. When I store the data in the local state, there is no way of passing the values up to the parent state, so that all tasks can be submitted in one click, but store unique values. I want to know if there is anyway for the values for each row to be stored in the components local state, so they are unique, but to pass the value up to the parent state, so they can all be submitted together? Or of the proper way you should deal with this in React?
The Code
So I have a parent component called TimesheetTool, that includes an empty array of tasks and the main table (each task the user wants to add to the timesheet tool is added as a table row). Then inside the table body I have mapped the tr to pull the table row from a child component called 'TableRow'.
Parent Component
import * as React from 'react';
import styles from './TimesheetTool.module.scss';
import { ITimesheetToolProps } from './ITimesheetToolProps';
import { escape } from '#microsoft/sp-lodash-subset';
import { ISpfxReactCrudState } from './ISpfxReactCrudState';
import { IListItem } from './IListItem';
import { SPHttpClient, SPHttpClientResponse } from '#microsoft/sp-http';
import TableRow from './TableRow';
export default class TimesheetTool extends React.Component<ITimesheetToolProps, ISpfxReactCrudState> {
constructor(props: ITimesheetToolProps, state: ISpfxReactCrudState) {
super(props);
this.state = {
project:'', task:'', mon:0, tues:0, wed:0, thurs:0, fri:0, sat:0, sun:0,
status: 'Ready',
items: [],
tasks: []
};
}
handleProjectChange = (event) => {this.setState({project: event.target.value}); console.log('Event:', event.target.value)}
handleTaskChange = (event) => {this.setState({task: event.target.value})};
handleMonChange = (event) => {this.setState({mon: event.target.value})};
handleTuesChange = (event) => {this.setState({tues: event.target.value})};
handleWedChange = (event) => {this.setState({wed: event.target.value})};
handleThursChange = (event) => {this.setState({thurs: event.target.value})};
handleFriChange = (event) => {this.setState({fri: event.target.value})};
handleSatChange = (event) => {this.setState({sat: event.target.value})};
handleSunChange = (event) => {this.setState({sun: event.target.value})};
handleAddTask = (task) => {
task.preventDefault();
const tasks = [...this.state.tasks];
this.setState({ tasks: this.state.tasks.concat(task) });
}
handleDelete = (index) => () => this.setState({tasks: this.state.tasks.filter((_, i) => i !== index)})
public render(): React.ReactElement<ITimesheetToolProps > {
const items: JSX.Element[] = this.state.items.map((item: IListItem, i: number): JSX.Element => {
return (
<li>{item.Title} ({item.Id}) </li>
);
});
return (
<div>
<form>
<button onClick={this.handleAddTask} >Add a Task</button>
<table className={styles.table}>
<thead>
<tr>
<th>Project</th>
<th>Task</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
</thead>
<tbody>
{this.state.tasks.map((task, index) =>
<tr key={index}>
<TableRow
handleProjectChange={this.handleProjectChange}
handleTaskChange = {this.handleTaskChange}
handleMonChange = {this.handleMonChange}
handleTuesChange = {this.handleTuesChange}
handleWedChange = {this.handleWedChange}
handleThursChange = {this.handleThursChange}
handleFriChange = {this.handleFriChange}
handleSatChange = {this.handleSatChange}
handleSunChange = {this.handleSunChange}
project={this.state.project}
task={this.state.task}
mon={this.state.mon}
tues={this.state.tues}
wed={this.state.wed}
thurs={this.state.thurs}
fri={this.state.fri}
sat={this.state.sat}
sun={this.state.sun}
/>
<td><a href="#" onClick={this.handleDelete(index)}>Delete</a></td>
</tr>
)}
</tbody>
</table>
<div>
<a href="#" onClick={() => this.createItem()}><span>Submit</span> </a>
</div>
</form>
<div>
{this.state.status}
<ul>
{items}
</ul>
</div>
</div>
);
}
private createItem(): void {
this.setState({
status: 'Creating item...',
items: []
});
const body: string = JSON.stringify({
'Title': `Submitted: ${new Date()}`,
'Project': `${this.state.project}`,
'Task': `${this.state.task}`,
'Monday': `${this.state.mon}`,
'Tuesday': `${this.state.tues}`,
'Wednesday': `${this.state.wed}`,
'Thursday': `${this.state.thurs}`,
'Friday': `${this.state.fri}`,
'Saturday': `${this.state.sat}`,
'Sunday': `${this.state.sun}`
});
this.props.spHttpClient.post(`${this.props.siteUrl}/_api/web/lists/getbytitle('${this.props.listName}')/items`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-type': 'application/json;odata=nometadata',
'odata-version': ''
},
body: body
})
.then((response: SPHttpClientResponse): Promise<IListItem> => {
return response.json();
})
.then((item: IListItem): void => {
this.setState({
status: `Timesheet Successfully Submitted`,
items: []
});
}, (error: any): void => {
this.setState({
status: 'Error while submitting timesheet' + error,
items: []
});
});
}
}
Child Component
import * as React from 'react';
import { Component, createRef } from 'react';
import { ITimesheetToolProps } from './ITimesheetToolProps';
import { ISpfxReactCrudState } from './ISpfxReactCrudState';
export default class TableRow extends React.Component<ITimesheetToolProps, ISpfxReactCrudState> {
render() {
return (
<React.Fragment>
<td><input type="text" name="project" value={this.props.project} onChange={(event) => this.props.handleProjectChange(event)} /></td>
<td><input type="textarea" name="task" value={this.props.task} onChange={(event) => this.props.handleTaskChange(event)} /></td>
<td><input type="number" name="mon" value={this.props.mon} onChange={(event) => this.props.handleMonChange(event)} /></td>
<td><input type="number" name="tues" value={this.props.tues} onChange={(event) => this.props.handleTuesChange(event)} /></td>
<td><input type="number" name="wed" value={this.props.wed} onChange={(event) => this.props.handleWedChange(event)} /></td>
<td><input type="number" name="thurs" value={this.props.thurs} onChange={(event) => this.props.handleThursChange(event)} /></td>
<td><input type="number" name="fri" value={this.props.fri} onChange={(event) => this.props.handleFriChange(event)}/></td>
<td><input type="number" name="sat" value={this.props.sat} onChange={(event) => this.props.handleSatChange(event)} /></td>
<td><input type="number" name="sun" value={this.props.sun} onChange={(event) => this.props.handleSunChange(event)} /></td>
</React.Fragment>
)
}
}
That because your each row binds with one single js object.
your code need to be changed as below
This is for first row
value={this.props[0].sun}
This is for second row
value={this.props[1].sun}
===================>
<tbody>
{this.state.tasks.map((task, index) =>
<tr key={index}>
<TableRow
project={this.state.project}
task={this.state.task}
mon={this.state.mon}
tues={this.state.tues}
wed={this.state.wed}
thurs={this.state.thurs}
fri={this.state.fri}
sat={this.state.sat}
sun={this.state.sun}
/>
<td><a href="#" onClick={this.handleDelete(index)}>Delete</a></td>
</tr>
)}
</tbody>
in this bit, you are passing data down to the tablerow component, but every row points to one js object which is this.state
You have to change to collection. it could be task.project something like. Anyhow, each row must point to one unique js object in your collection of data.
I think you need to do a bit separation of concern where the Parent will manage your list of tasks while the Child component will handle each. Something like below should work for you.
// Parent
export default class TimesheetTool extends React.Component<ITimesheetToolProps, ISpfxReactCrudState> {
...
onUpdateTask = (index, item) => {
this.setState(prevState => ({
task: prevState.task.map((task, i) => {
return index === i ? item : task
})
})
}
...
render () {
...
{this.state.tasks.map((task, index) =>
<tr key={index}>
<TableRow index={index} onUpdateTask={this.onUpdateTask} /> // pass down a callback
</tr>
)}
...
}
})
// Child
export default class TableRow extends React.Component<ITimesheetToolProps, ISpfxReactCrudState> {
constructor() {
this.state = {
project:'',
task:'',
mon:0,
tues:0,
...
};
}
onChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value,
}, () => {
this.props.onUpdateTask(this.props.index, this.state) // call the parent that the task of this index has changed
})
}
render() {
return (
<React.Fragment>
<td><input type="text" name="project" value={this.state.project} onChange={this.onChange} /></td>
<td><input type="textarea" name="task" value={this.state.task} onChange={this.onChange} /></td>
<td><input type="number" name="mon" value={this.state.mon} onChange={this.onChange} /></td>
<td><input type="number" name="tues" value={this.state.tues} onChange={this.onChange} /></td>
...
</React.Fragment>
)
}
}

Updating React state with Hooks and tags

I'm having a syntax doubt on how to update React state using hooks in 2 situations.
1) I have a state called company and a form that fills it up. In contact section, there are two inputs referring to the company employee (name and telephone number). But if the company has more than one employee to be contacted, there is an "Add More Contact" button, which must duplicate the same inputs (of course, aiming to a second contact). How can I do that? I mean, to generate another index in the array "contacts" inside the state, increment the totalOfContacts inside the object that has that array and create the input tags so user can type the second contact's data?
2) When I type any inputs, the code triggers the handleChange function. The "name" and "city" already update the state because they are simple states. But how can I update the contact name and his telephone number, since they are part of an index of an array inside the state?
The code below is already working and my 2 questions are exactly the two commented lines (lines 20 and 29).
The "Save" button simply console.log the results so we can monitor them.
Thanks for now.
import React, { useState, useEffect } from "react";
export default () => {
const [company, setCompany] = useState({
name: "", city: "",
contact: {
totalOfContact: 1,
contacts: [
{id: 0, contactName: "", telephoneNumber: ""}
]
}
})
useEffect(() => {
console.log("teste");
})
const handleChange = item => e => {
if (item === "contactName" || "telephone") {
// How can I set company.contact.contacts[<current_index>].contactName/telephoneNumber with the data typed?
} else {
setCompany({ ...company, [item]: e.target.value })
}
}
const handleClick = (e) => {
e.preventDefault();
if (e.target.value === "add") {
// How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
} else {
console.log(`The data of the company is: ${company}`);
}
}
return (
<div>
<form>
<h3>General Section</h3>
Name: <input type="text" onChange = {handleChange("name")} value = {company.name} />
<br />
City: <input type="text" onChange = {handleChange("city")} value = {company.city} />
<br />
<hr />
<h3>Contacts Section:</h3>
Name: <input type="text" onChange = {handleChange("contactName")} value = {company.contact.contacts[0].name} />
Telephone Numer: <input type="text" onChange = {handleChange("telephone")} value = {company.contact.contacts[0].telephoneNumber} />
<br />
<br />
<button value = "add" onClick = {(e) => handleClick(e)} >Add More Contact</button>
<br />
<br />
<hr />
<button value = "save" onClick = {(e) => handleClick(e)} >Save</button>
</form>
</div>
)
}
To update the state value, you can use functional setState,
const handleChange = item => e => {
//Take the value in a variable for future use
const value = e.target.value;
if (item === "contactName" || "telephone") {
setCompany(prevState => ({
...prevState,
contact: {...prevState.contact, contacts: prevState.contact.contacts.map(c => ({...c, [item]: value}))}
}))
} else {
setCompany({ ...company, [item]: e.target.value })
}
}
To add new set of input on the click of button you can do this,
const handleClick = (e) => {
e.preventDefault();
//This is new set of input to be added
const newSetOfInput = {id: company.contact.contacts.length, contactName: "", telephoneNumber: ""}
if (e.target.value === "add") {
// How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
setCompany(prevState => ({
...prevState,
contact: {...prevState.contact, contacts: prevState.contact.contacts.concat(newSetOfInput), totalOfContact: prevState.contact.contacts.length + 1}
}))
} else {
console.log(`The data of the company is: ${company}`);
}
}
Finally you need to iterate over your contacts array like,
{company.contact.contacts && company.contact.contacts.length > 0 && company.contact.contacts.map(contact => (
<div key={contact.id}>
Name: <input type="text" onChange = {handleChange("contactName")} value = {contact.contactName} />
<br/>
Telephone Numer: <input type="text" onChange = {handleChange("telephoneNumber")} value = {contact.telephoneNumber} />
</div>
))}
Demo
Note: You should use block elements like div instead of breaking the line using <br/>
To answer your question let us scope down this problem to a much simpler problem, which is how to handle array of contacts.
You just need know the following things:
Map function
How to update array without mutating the original array
I'll use TypeScript so you can understand better.
const [state, setState] = React.useState<{
contacts: {name: string}[]
}>({contacts: []})
return (
<div>
{state.contacts.map((contact, index) => {
return (
<div>
Name:
<input value={contact.name} onChange={event => {
setState({
...state,
contacts: state.contacts.map((contact$, index$) =>
index === index$
? {...contact$, name: event.target.value}
: {...contact$}
)
})
}}/>
</div>
)
}}
</div>
)
Also, this kind of problem is fairly common in React, so understand and memorize this pattern will help you a lot.
You can do something like this.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [company, setCompany] = useState({
name: "",
city: "",
contact: {
totalOfContact: 1,
contacts: [{id: 0, contactName: "", telephoneNumber: ""}]
}
});
console.log(company);
useEffect(() => {
console.log("teste");
}, []);
const handleChange = (item, e,index) => {
if (item === "contactName" || item === "telephoneNumber") {
const contactsNew = [...company.contact.contacts];
contactsNew[index] = { ...contactsNew[index], [item]: e.target.value };
setCompany({
...company,
contact: { ...company.contact, contacts: contactsNew }
});
// How can I set company.contact.contacts[<current_index>].contactName/telephoneNumber with the data typed?
} else {
setCompany({ ...company, [item]: e.target.value });
}
};
const handleClick = e => {
e.preventDefault();
if (e.target.value === "add") {
const contactNew = {...company.contact};
contactNew.totalOfContact = contactNew.totalOfContact + 1;
contactNew.contacts.push({id:contactNew.totalOfContact -1, contactName: "", telephoneNumber: ""});
setCompany({...company, contact: {...contactNew}});
// How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
} else {
alert("Push company to somewhere to persist");
console.log(`The data of the company is: ${company}`);
}
};
return (
<div>
<form>
<h3>General Section</h3>
Name:{" "}
<input
type="text"
onChange={(e) => handleChange("name", e)}
value={company.name}
/>
<br />
City:{" "}
<input
type="text"
onChange={(e) => handleChange("city", e)}
value={company.city}
/>
<br />
<hr />
<h3>Contacts Section:</h3>
{company.contact.contacts.map((eachContact, index) => {
return <React.Fragment>
Name:{" "}
<input
type="text"
onChange={(e) => handleChange("contactName",e, index)}
value={eachContact.name}
/>
Telephone Numer:{" "}
<input
type="text"
onChange={(e) => handleChange("telephoneNumber",e, index)}
value={eachContact.telephoneNumber}
/>
<br />
</React.Fragment>
})}
<br />
<button value="add" onClick={e => handleClick(e)}>
Add More Contact
</button>
<br />
<br />
<hr />
<button value="save" onClick={e => handleClick(e)}>
Save
</button>
</form>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Your state structure looks like an ideal candidate for useReducer hook. I would suggest you try that instead of useState. Your code should look muck readable that way, I suppose. https://reactjs.org/docs/hooks-reference.html#usereducer

TypeError: this.setState.myItems is undefined

Im pretty sure the code is correct but i still get an error
"TypeError: this.setState.myItems is undefined"
when i press the remove button
I tried to remove the specific item using the setState in filter() method
It says the error is at line 35 :
const filteredItems = this.setState.myItems.filter(myItem => {
over there
import React from "react";
import PropTypes from "prop-types";
import { returnStatement, isTemplateElement } from "#babel/types";
import "./style.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myItems: ["Poco F1", "OnePlus 6", "MiA1", "S7 Edge"]
};
}
addItem(e) {
e.preventDefault();
const { myItems } = this.state;
const newItem = this.newItem.value;
const exists = myItems.includes(newItem);
if (exists) {
this.setState({
message: "This Phone is already listed"
});
} else {
newItem !== "" &&
this.setState({
myItems: [...this.state.myItems, newItem]
});
}
this.newForm.reset();
}
removeItem(item) {
const filteredItems = this.setState.myItems.filter(myItem => {
return myItem !== item;
});
this.setState({
myItems: [...filteredItems]
});
}
render() {
const { myItems, message } = this.state;
return (
<div>
<h1>
<b>PHONE LIST</b>
</h1>
<form
ref={input => (this.newForm = input)}
onSubmit={e => this.addItem(e)}
>
<div className="form-group">
<label className="sr-only" htmlFor="newItemInput">
{" "}
ADD NEW PHONE
</label>
<input
type="text"
ref={input => (this.newItem = input)}
placeholder="Enter Phone Name"
className="form-control"
id="newItemInput"
/>
</div>
<button type="submit">ADD</button>
</form>
<div className="content">
<table className="table">
<thead>
<tr>
<th>Number</th>
<th>Phone Name</th>
<th>add/remove</th>
</tr>
</thead>
<tbody>
{myItems.map(item => {
return (
<tr key={item}>
<td scope="row">1</td>
<td>{item}</td>
<td>
<button
onClick={e => this.removeItem(item)}
type="button"
>
REMOVE
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<ol />
</div>
);
}
}
export default App;
I want to remove the specific item using the setState in filter() method
Change this.setState.myItems to this.state.myItems
this.state.myItems instead of this.setState.myItems :)

Getting text inputs to work in ReactJS

I have a few text inputs on my form that are not working correctly. I cannot see what is being typed and the fields unfocus after just one character.
Here's the pieces of my code where it's at:
constructor(props) {
super(props);
this.state = {
key: uuid(),
title: "",
author: "",
questions: [],
answers: []
};
this.handleChange = this.handleChange.bind(this);
this.addQuestion = this.addQuestion.bind(this);
this.removeItem = this.removeItem.bind(this)
}
componentDidMount() {
// componentDidMount() is a React lifecycle method
this.addQuestion();
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
//yada yada
render() {
//yada yada
{this.state.questions.map((question, index) => {
return (
<li>
<div key={uuid()}>
Question
{question}<br />
<button onClick={ () => this.removeItem(index) }>
Remove Question
</button>
Answer Choices<br />
{Array.from({ length: 4 }, () => (
<div>
<input type="checkbox" key={uuid()}/>
<input type="text" key={uuid()} onChange={this.handleChange} />
</div>
))}
</div>
</li>
);
})}
//yada yada
}
export default App;
I am very new to this so any tips directly related to this issue or not would be super helpful. Thanks!
You are giving your elements unique keys each render with uuid(), which will destroy the element and mount a new one. Remove the key={uuid()} and it will work.
{this.state.questions.map((question, index) => {
return (
<li key={index}>
<div>
Question
{question}
<br />
<button onClick={() => this.removeItem(index)}>
Remove Question
</button>
Answer Choices<br />
{Array.from({ length: 4 }, (_element, index) => (
<div key={index}>
<input type="checkbox" />
<input type="text" onChange={this.handleChange} />
</div>
))}
</div>
</li>
);
})}

Put cursor inside input box onChange in React

I have a component of basic editable list items that operates as such:
My problem is that the cursor does not move into the input box onChange, making a hassle for the user to always click twice. I tried https://coderwall.com/p/0iz_zq/how-to-put-focus-at-the-end-of-an-input-with-react-js but it did not work. Component looks like:
import React from 'react';
import MyComponent from '../utils/MyComponent';
export default class BasicList extends MyComponent {
constructor(props) {
let custom_methods = ['renderItemOrEditField', 'toggleEditing', 'moveCaretAtEnd'];
super(props, custom_methods);
this.state = {editing: null};
}
moveCaretAtEnd(e) {
var temp_value = e.target.value
e.target.value = ''
e.target.value = temp_value
}
renderItemOrEditField(item) {
console.log(item);
if (this.state.editing === item.id) {
return (
<input
onKeyDown={ this.handleEditField }
type="text"
className="form-control"
ref={ `${item.type}_name_${ item.id }` }
name="title"
autofocus
onFocus={this.moveCaretAtEnd}
defaultValue={ item.name }
/>
);
} else {
return (
<li
onClick={this.toggleEditing.bind(null, item.id)}
key={item.id}
className="list-group-item">
{item.name}
</li>
);
}
}
toggleEditing(item_id) {
this.setState({editing: item_id});
}
render() {
let li_elements = null;
let items = this.props.items;
if (items.length > 0) {
li_elements = items.map((item) => {
return (
this.renderItemOrEditField(item)
// {/* }<li key={item.id}>
// {item.name} -
// <button onClick={() => {this.props.deleteCallback(this.props.item_type, item.id, item.name)} }>
// Delete
// </button>
// </li> */}
);
});
}
return (
<div>
<h4>{this.props.title}:</h4>
<ul className="list-group">
{li_elements}
</ul>
</div>
);
}
}
The items I'm working with now only have a name and ID (type denotes a 'role' or 'task')
How can I make the cursor start at the end of the input box text on change?
Autofocus triggers when a component is mounted. Not sure that's happening by your conditional render. You could moving your conditional logic to a separate container and that should trigger a mount each time it's shown.
I ended up setting focus as in the callback that causes the input box to show:
toggleEditing(item_id) {
// this syntax runs the function after this.setState is finished
this.setState({editing: item_id}, function() {
this.textInput.focus();
});
}
Then the original solution given worked:
// https://coderwall.com/p/0iz_zq/how-to-put-focus-at-the-end-of-an-input-with-react-js
moveCaretAtEnd(e) {
var temp_value = e.target.value
e.target.value = ''
e.target.value = temp_value
}
and
<input
onKeyDown={ this.handleEditField }
type="text"
className="form-control"
ref={(input) => { this.textInput = input; }}
name="title"
autofocus
onFocus={this.moveCaretAtEnd}
defaultValue={ item.name }
onChange={(event) => this.editItem(event)}
style={ {maxWidth: 500} }
/>

Categories