React- input field turns to string after onChange - javascript

I have a small app with three components
parent => App
Son => Courses
Grandchild => Course
The app state lies in the App component, in the Course component I have an input field with an onChange event that spouse to change the state in the app component, the problem is that every time I type the input changes to a string and I can't keep typing and changing the state.
The values arrive to the parent.
This is my Course code
import React, { Component } from 'react'
class Course extends Component {
updatedGrade = (e) => {
this.props.updateCourseGrade(Number(e.target.value), Number(e.target.id));
};
render() {
const {id, courseType, courseName, courseGrade} = this.props.course;
return (
<tr key={id}>
<td>
{courseName}
</td>
<td>
{(courseType ? 'a' : 'm' )}
</td>
<td>
{(courseGrade !== ''
? courseGrade
: <input
type="number"
id={id}
onChange={this.updatedGrade}
value={courseGrade}
/>
)}
</td>
<td>
<button
onClick={this.props.removeCourse.bind(this, id)}
style={btnStyle}
>
remove
</button>
</td>
</tr>
)
}
}
this is my App relevant code:
class App extends Component {
state = {
courses: [
{
id: 1,
courseName: 'bioliogy,
courseType: false,
courseHours: 10,
courseGrade: ''
},{
id: 2,
courseName: 'Mahematics,
courseType: true,
courseHours: 20,
courseGrade: ''
},{
id: 3,
courseName: 'History,
courseType: false,
courseHours: 30,
courseGrade: 50
}
]
};
updateCourseGrade(courseGrade, id){
//const courseGradeNum = Number(courseGrade);
this.setState({
courses: this.state.courses.map(course => course.id === id ? {...course, courseGrade } : course)
})
console.log('courseGrade ', courseGrade);
Now, when I do this:
updateCourseGrade(courseGrade, id){
const courseGradeNum = Number(courseGrade);
this.setState({
courses: this.state.courses.map(course => course.id === id ? {...course, courseGradeNum } : course)
})
The state will get a new value while typing named courseGrade and I don't want this.
as well the courseGrade is already defined as a Number in the Course component
What can I do? maybe I shouldn't use value in the course component?
UPDATE
According to Freeman Lambda request, this is the state after I change the value in the input field,
the state of courseGrade of the desired course changes. but because the input field disappears I cannot keep typing.
Link to a video that shows what happens
https://www.screencast.com/t/Cyz1v6zMWsq

Here:
{(courseGrade !== ''
? courseGrade
: <input
type="number"
id={id}
onChange={this.updatedGrade}
value={courseGrade}
/>
)}
You explicitely change the input to a plain string a soon as courseGrade is !== ''
if you want to be able to keep typing you have to stick with an input during typing. If you want the input to disapear after typing you will have to add a button controlling a state that removes the input, for example:
class Course extends Component {
state = {
gradeValidated: false,
}
updatedGrade = (e) => {
this.props.updateCourseGrade(Number(e.target.value), Number(e.target.id));
};
toggleGradeInput = (e) => {
this.setState((state) => ({ gradeValidated: !state.gradeValidated }));
};
render() {
const {id, courseType, courseName, courseGrade} = this.props.course;
return (
<tr key={id}>
<td>
{courseName}
</td>
<td>
{(courseType ? 'a' : 'm' )}
</td>
<td>
{(this.state.gradeValidated
? courseGrade
: <input
type="number"
id={id}
onChange={this.updatedGrade}
value={courseGrade}
/>
)}
</td>
<td>
<button
onClick={this.toggleGradeInput}
style={btnStyle}
>
toggle input
</button>
<button
onClick={this.props.removeCourse.bind(this, id)}
style={btnStyle}
>
remove
</button>
</td>
</tr>
)
}
}

The problem is here
courseGrade !== ''
? courseGrade
: <input
type="number"
id={id}
onChange={this.updatedGrade}
value={courseGrade}
/>
the first condition is set to true if you set any state instead of '', so my idea is to use a save button for the grate and onChange keep on a local state.

You should use an updater function for this, I think what is happening is that state updates are getting batched.
So instead of
this.setState({
courses: this.state.courses.map(course => course.id === id ? {...course, courseGradeNum } : course)
})
try
this.setState( prevState => ({
courses: prevState.courses.map(course => course.id === id ? {...course, courseGradeNum } : course)
}))
If this wasn't the problem can you create a code sandbox because there just isn't enough code to understand what is going on. For example, updateCourseGrade is being bound anywhere. don't know if you haven't or if you are just not showing it.

Related

Radio button not working correctly when rendered using array Data

I have a React component that renders an array with an input field and a radio button. The input field is working fine but when clicking the radio button even though the state gets updated, the value is not reflected in the UI.
Following is the implementation of the React component
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [subTemplate, setSubTemplate] = useState([
{ name: 'main', outputFormat: 'html' },
{ name: 'something else', outputFormat: 'text' }
]);
const handleInputChange = (value, row, key) => {
const updatedSubTemplate = subTemplate.map(item => {
if (item.name === row.name) {
return {
...item,
[key]: value
};
}
return item;
});
console.log('updatedSubTemplate', updatedSubTemplate); // updatedSubTemplate consists of the modified value
setSubTemplate(updatedSubTemplate);
};
const renderSubTemplates = () => {
return subTemplate.map((item, index) => {
return (
<div key={index}>
<input
type="text"
value={item.name}
onChange={e => {
handleInputChange(e.target.value, item, 'name');
}}
/>
<div>
<input
type="radio"
name="html"
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
HTML
</div>
<div>
<input
type="radio"
name="text"
checked={item.outputFormat === 'text'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
TEXT
</div>
</div>
);
});
};
return <div>{renderSubTemplates()}</div>;
}
Following is a stackblitz link for the above implementation : https://stackblitz.com/edit/react-ugat24
Note: The radio button works as expected if there's only 1 element in the array.
It is because you have different value in name attribute.
Radio buttons are used to select one option from a list of options. so they need to have the same name to group them .
<input
type="radio"
name={`group-${index}`}
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange('html', item, 'outputFormat');
}}
/>
Working Sample
https://stackblitz.com/edit/react-4xxpev
There are 2 issues in your code.
You are passing the same name attribute for all the radio inputs.
You are passing the wrong value for the radio elements.
Use template literals to append the index value to each set of radio group elements in the array.
Here's working code
References
Radio Input
Template literals

Nested Object Iteration with conditional statements ReactJs

I have the below api call from React Js. It is a class component. am new to React Js.
class UserComponent extends React.Component {
constructor(props){
super(props)
this.state=({
users: []
});
retrieve(e){
e.preventDefault();
fetch(`http://localhost:8080/test/{this.state.id}`,{
"method":"GET",
"headers":{
"accept":"application/json"
}
})
.then(response => response.json())
.then(response => {
this.setState({
users:response
})
}
render(){
return (
<div>
<form>
<table>
<tbody>
{
this.state.users === '' || null ? '' : Object.entries(this.state.users).map([key,value])=>(
Object.entries(key).map(field=>(
<tr>
<td>
<input name = "firstName" id = firstName" defaultValue = {field.firstName} required/>
</td>
</tr>
))))
}
</tbody>
</table>
</form>
</div>
)
}
}
export default UserComponent
I need to get the firstName and lastName and other details from a simple json file
{
"id":"12224343",
"depId":"1",
"employees":[{
"empId":"1",
"firstName":"sample",
"lastName":"test",
"address":[{
"street":"1",
"pin":"12345"
}]
}]
}
I need to get the firstname and last name of this nested object and address's object's value also. Is there any way like flatmap kind of thing is available in react class component. I didn't use useEffects and all.
when I tried to iterate it am getting "map is not a function. it is undefined"
the class component with state objects have any restrictions. tried a lot of options.
I need to iterate the object of object and fetch only particular values in the UI test box. like first name and lastname. now it is iterating all and displaying all in the UI. which looks messy.
Please help me to get a solution for this.
Since you initialized state users as an array. You can just use users.map(item). Each item contains multiple employees. You can also convert data from the current json file to any data structure which is comfortable for you to display in the table.
const {users} = this.state
users?.map((user,index) => {
return (
<div key={key.id}>
{user?.employees.map(item => {
return (
<div key={item.empId + key.id}>
<input defaultValue = {item.firstName} required/>
</div>
)
})}
</div>
)
} )
I suggest to choose open source UI library such as Material UI or Antd to display data inside table. It saves time to manage responsive web page styles.
I have initialized an empty return object that fixes all the problems and it worked like a charm. please check the below details.
class UserComponent extends React.Component {
constructor(props){
super(props)
this.state=({
users : [{
"id":"",
"depId":"",
"employees":[{
"empId":"",
"firstName":"",
"lastName":"",
"address":[{
"street":"",
"pin":""
}]
}]
}]
});
retrieve(e){
e.preventDefault();
fetch(`http://localhost:8080/test/{this.state.id}`,{
"method":"GET",
"headers":{
"accept":"application/json"
}
})
.then(response => response.json())
.then(response => {
this.setState({
users:response
})
}
render(){
return (
<div>
<form>
<table>
<tbody>
{
this.state.users === '' || null ? '' : Object.entries(this.state.users).map([key,value])=>(
Object.entries(key).map(field=>(
<tr>
<td>
<input name = "firstName" id = firstName" defaultValue = {field.firstName} required/>
</td>
</tr>
))))
}
</tbody>
</table>
</form>
</div>
)
}
}
export default UserComponent

How to re-render a list after deleting an element

I am trying to write a delete method in order to delete an element from a list, first of all I am not being able to write it in a setState function so I have it as a direct function call, How can I manage to signal a re-render after the direct function or manage to place the function in the setState method for automatic re-render?
class TASKMANAGER extends Component {
constructor(props){
super(props);
this.state= {
name: "",
description:"",
priority: "urgent",
tasklist: [],
}
this.handleTitleChange= this.handleTitleChange.bind(this);
//this.handleDescriptionChange= this.handleDescriptionChange.bind(this);
//this.handlePriorityChange= this.handleDescriptionChange.bind(this);
this.handleClick= this.handleClick.bind(this);
}
handleTitleChange = event => {
this.setState( {
name: event.target.value
})
};
handleDescriptionChange = event => {
this.setState({
description: event.target.value
})
};
handlePriorityChange = event => {
this.setState({
priority: event.target.value
})
};
handleClick = event => {
this.setState((state) => {
const tasklist = [
...state.tasklist,
[
state.name,
state.description,
state.priority
]
];
return {
tasklist
};
});
//console.log(this.state.tasklist);
};
handleDelete = index => {
this.setState(() => {
this.state.tasklist.splice(index, 1)
});
console.log(this.state.tasklist)
} THIS ONE IS THE FUNCTION I CANNOT SET TO WORK TO TRIGGER THE AUTO RE-RENDER
render() {
const task_item = this.state.tasklist.map((arr, index) => (
<li
key= {index}
className= 'task'>
Task: {arr[0]} <br />
Description: {arr[1]} <br />
Priority: {arr[2]} <br />
<div className='delete-button' onClick={
/*() => {this.state.tasklist.splice(index, 1);}*/ THIS ONE IS THE DIRECT FUNCTION THAT WORKS, BUT DOESN'T TRIGGER THE RE-RENDER, IT SHOWS WHEN I TYPE AGAIN ON THE INPUTS
this.handleDelete
}>delete</div>
</li>
))
return (
<div>
<div className= 'task-form'>
<form>
<div>
<label>Name your task!</label>
<input type= 'text' id='task-title' value={this.state.name} onChange={this.handleTitleChange} />
</div>
<div>
<label>Description?</label>
<textarea id='description' value={this.state.description} onChange={this.handleDescriptionChange}/>
</div>
<div>
<label>Priority?</label>
<select value={this.state.priority} onChange={this.handlePriorityChange}>
<option value='urgent'>Urgent</option>
<option value='regular'>Regular</option>
<option value='Can wait'>Can wait</option>
</select>
</div>
</form>
<button onClick={this.handleClick}>PRESS</button>
</div>
<div className='list-items'>
<ul className='list-render'>
{task_item}
</ul>
</div>
</div>
)
}}
export default TASKMANAGER
You shouldn't be making any mutations to the current state, but instead build a new state from the existing state, generating a new, filtered array along the way
handleDelete = index => {
this.setState((state) => ({
...state,
tasklist: state.taskList.filter((_,i) => i != index)
}));
}
When you map your taskList to JSX below, you will need to avoid using the index of the item as key, because the optimizations react makes using the key value will be operating under broken assumptions. Use a key value that remains constant and unique per item. Perhaps its name, or an identifier that is attached to it when created.
There is no need to assign the list. Just splice it. Use something like this to change the state:
delete={()=>{this.setState({phase:1-this.state.phase});
this.state.list.splice(index,1)}}

Inserting only unique ID in form value in React

I am new to React, there are two input fields in the application, one is for ID and another for Name, There are two components I've used, in the parent component I've maintained all the state and form in separate another component. My aim is to check the id which is a input from the user, id should be unique every time, if it's same, an alert should popup and the focus turns to ID input field, and it should do the same until the ID is different from all the objects(state object)
My app.js file is,
import React, { Component } from "react";
import Form from "./Form";
export default class App extends Component {
state = {
names: [
/*
{id: 1,name: "Aashiq"}
*/
],
};
renderTable() {
return this.state.names.map((eachName) => {
const { id, name } = eachName;
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>
<input
type="button"
value="Delete"
onClick={() => this.deleteName(eachName.id)}
/>
</td>
</tr>
);
});
}
deleteName = (id) => {
console.log("ID object", id);
this.state.names &&
this.setState({
names: this.state.names.filter((name) => name.id !== id),
});
};
addName = (newName) => {
this.setState({
names: [newName, ...this.state.names],
});
};
render() {
return (
<>
<Form onSubmit={this.addName} names={this.state.names} />
{/* Table */}
<br />
<table id="details">
<tbody>
<tr>
<th>ID</th>
<th>Names</th>
<th>Operation</th>
</tr>
{/* Render dynamic rows
*/}
{this.renderTable()}
</tbody>
</table>
</>
);
}
}
You can see I try to render the data as table and we can delete the row data also
The form.js file is,
import React, { useState } from "react";
// import { uniqueId } from "lodash";
export default function Form(props) {
const [name, setName] = useState("");
const [id, setId] = useState();
const handleSubmit = (e) => {
e.preventDefault();
handleChangeandValidate();
};
const handleChangeandValidate = () => {
const { onSubmit, names } = props;
console.log("Object keys length", Object.keys(names).length);
if (Object.keys(names).length !== 0) {
names.map((name) => {
if (name.id === id) {
alert("Enter unique id");
setId("");
document.getElementById("ID").focus();
} else {
//if different id
onSubmit({ id: id, name: name });
setName("");
setId("");
}
return null;
});
} else {
onSubmit({ id: id, name: name }); // first time
setName("");
setId("");
}
};
return (
<form onSubmit={handleSubmit} id="myform">
<label style={{ fontSize: "20px", fontWeight: "bold" }}>
Name: {""}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</label>{" "}
<label style={{ fontSize: "20px", fontWeight: "bold" }}>
ID: {""}
<input
type="number"
onChange={(e) => setId(e.target.value)}
required
value={id}
id="ID"
/>
</label>
{""}
<input type="submit" value="Submit" />
</form>
);
}
You can see I've tried to get the state and onSubmit function from the parent component(app.js) and done some logic like comparing all the ID's, but this logic throws some error, please somebody come up with a good solution.
Thanks in advance!
I have modified your code a bit and here is a working example.
Here is what I did:
I used createRef() to create two references that refer to each input field named nameInputRef and idInputRef.
I added ref={nameInputRef} and ref={idInputRef} so that we can get their values on submit.
On submit, I get the values of the name + id using their refs.
to search for whether the ID exists or not, I used Array.find() which would return undefined if the same id doesn't exist in the list of names coming from the props.
in addName(), I used setState() but in the param I used a function to make sure I get the latest list of names as updating the state is asynchronous. Inside I also used ES6's destructuring feature to make a copy of the current list, push the new name to it and then update the state with the new list of names.

Filtering data in table in React based on values of radio buttons and input field after hitting submit failed

I have data from local json file that I use to create a table.
In Table class component I have the table contains top 10 movies. The data is being displayed from filteredData state variable and are well displayed after loading the table. Above table I have 2 radio buttons, to choose whether I want to search data based on column title or column genre saved in state variable radioSearch by using function searchHandler. Then I have an input field, when I enter a string in it the result is being saved in searchFieldInput state variable, by using updatedSearch function.
Finally, I have submitHandler function in this component to filter the table based on selected radio button(title/genre of the film), and after that based on entered string in input field. The filtered data I am putting into filteredData variable in order to update the state by using setState. Unfortunately no filtering is being done after hitting submit. In Table component is nested TableRow component which should display the data based on applied filtering. I don't know whether the concept of submitHandler function is wrong, and why is not filtering the data? Can somebody help.
Here is my Table component:
import React, {Component} from 'react';
import TableRow from './TableRow/TableRow';
class Table extends Component {
constructor(props) {
super(props)
this.state = {
filteredData: this.props.data,
searchFieldInput: '',
radioSearch: this.props.radioSearch,
transformed: false
}
}
updatedSearch = (event) => {
this.setState({
searchFieldInput: event.target.value
})
}
searchHandler = (e) => {
this.setState({
radioSearch: e.target.value
})
};
submitHandler = (event) => {
event.preventDefault();
if(this.state.radioSearch === "title") {
let filteredData = this.props.data.filter(column => {
return column.title.toLowerCase().indexOf(this.state.searchFieldInput.toLowerCase()) !== -1;
});
this.setState({
filteredData: filteredData
});
return this.state.filteredData;
} else if(this.state.radioSearch === "genre"){
let filteredData = this.props.data.filter(column => {
return column.genre.toLowerCase().indexOf(this.state.searchFieldInput.toLowerCase()) !== -1;
});
this.setState({
filteredData: filteredData
});
return this.state.filteredData;
}
console.log(this.state.radioSearch);
}
render() {
let filteredData = this.props.data.filter(column => {
return column.title.toLowerCase().indexOf(this.state.searchFieldInput.toLowerCase()) !== -1;
});
return(
<React.Fragment>
<div className="container-fluid">
<div className="container">
<form>
{/*Search field*/}
<input
className={"Search" + (this.state.transformed === true ?
' transformed' : '')}
type="text"
placeholder={(this.state.transformed === true ?
'' : 'Type here')}
maxLength="20"
value={this.state.searchFieldInput} required
onChange={this.updatedSearch.bind(this)}
/>
<button type="submit">
Search
</button>
{/*Radio buttons*/}
<label htmlFor="title">
<input type="radio" name="title" id="title" value="title" checked={this.state.radioSearch === "title"}
onChange={this.searchHandler}/>
title
</label>
<label htmlFor="genre">
<input type="radio" name="genre" id="genre" value="genre" checked={this.state.radioSearch === "genre"}
onChange={this.searchHandler}/>
genre
</label>
</form>
</div>
<div className="container">
<table>
<thead>
<tr>
<th>No.</th>
<th>Picture</th>
<th>Release date</th>
<th>Genre</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{this.state.filteredData.map((row, index) => {
return (
<TableRow
numeration={index + 1}
key={row.id}
row={row}
/>
)
})
}
</tbody>
</table>
</div>
</div>
</React.Fragment>
)
}
}
export default Table;
I think its because you forgot to add the function to the submit button:
<button type="submit" onSubmit={this.submitHandler.bind(this)}>
Search
</button>

Categories

Resources