pass id in react.js to make update view - javascript

I have listing view working correctly and I want to pass data to update view by Id - in URL Id is passed correctly, but without data saved to this Id. In console log id is tagged as undefined
UpdateCar.jsx
import React, { Component } from 'react';
import CarServices from '../../Services/CarServices';
class UpdateCar extends Component {
constructor(props) {
super(props)
this.state = {
carId: this.props.match.id,
brand: '',
color: ''
}
this.changeBrandHandler = this.changeBrandHandler.bind(this);
this.changeColorHandler = this.changeColorHandler.bind(this);
this.getCarId = this.getCarId.bind(this);
this.updateCar = this.updateCar.bind(this);
}
componentDidMount() {
CarServices.getCarById(this.state.carId).then((res) => {
let car = res.data;
this.setState({
brand: car.brand,
color: car.color
});
});
}
changeBrandHandler = (event) => {
this.setState({ brand: event.target.value });
}
changeColorHandler = (event) => {
this.setState({ color: event.target.value });
}
updateCar = (e) => {
e.preventDefault();
let car = { brand: this.state.brand, color: this.state.color };
console.log('test: ' + JSON.stringify(car));
console.log('id => ' + JSON.stringify(car.carId));
}
cancel() {
this.props.history.push('/showCars');
}
render() {
return (
<div>
<div className='container'>
<div className='row'>
<div className='card col-md-6 offset-md-3 offset-md-3'>
<h3 className='text-center'> Edit car </h3>
<div className='cardBody'>
<form>
<div className='form-group'>
<label> Brand: </label>
<input placeholder="brand" name="brand" className="form-control"
value={this.state.brand} onChange={this.changeBrandHandler} />
<label> Color: </label>
<input placeholder="color" name="color" className="form-control"
value={this.state.color} onChange={this.changeColorHandler} />
</div>
<button className="btn btn-success" onClick={this.updateCar}>Save</button>
<button className="btn btn-danger" onClick={this.cancel.bind(this)} style={{ marginLeft: "10px" }}>Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default UpdateCar;
CarServices.js
When I hardcode id in url it works correclty - I don't know why I don't have any data from database in placeholders
import axios from 'axios';
const CAR_API_BASE_URI = "http://localhost:8080/car"
class CarServices{
getCars(){
return axios.get(CAR_API_BASE_URI);
}
addCar(car){
return axios.post(CAR_API_BASE_URI, car);
}
getCarById(id){
return axios.get(CAR_API_BASE_URI + '/' + id);
//return axios.get("http://localhost:8080/car/2"); - correclty view data from database saved with id:2
}
}
export default new CarServices();
ListCar.jsx
import React, { Component } from 'react';
import CarServices from '../../Services/CarServices';
class ListCar extends Component {
constructor(props){
super(props)
this.state = {
cars: []
}
this.addCar = this.addCar.bind(this);
this.editCar = this.editCar.bind(this);
}
addCar(){
this.props.history.push('/addCar');
}
editCar(id){
this.props.history.push(`/editCar/${id}`);
}
componentDidMount(){
CarServices.getCars().then((res)=>{
this.setState({ cars: res.data})
})
}
render() {
return (
<div>
<h2 className='text-center'>Car list </h2>
<div className='row'>
<button className='btn btn-primary' onClick={this.addCar} style={{marginLeft: "15px"}} >Add car</button>
</div>
<div className='row'></div>
<table className='table table-striped table-bordered'>
<thead>
<tr>
<th className='text-center'>Id</th>
<th className='text-center'>brand</th>
<th className='text-center'>color</th>
<th className='text-center'>action</th>
</tr>
</thead>
<tbody>
{
this.state.cars.map(
car =>
<tr key = {car.carId}>
<td className='text-center'>{car.carId}</td>
<td className='text-center'>{car.brand}</td>
<td className='text-center'>{car.color}</td>
<td className='text-center'>
<button onClick ={ () => this.editCar(car.carId)} className="btn btn-info">Update </button>
<button style={{marginLeft: "10px"}} className="btn btn-danger">Delete </button>
<button style={{marginLeft: "10px"}} className="btn btn-info">View </button>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
export default ListCar;

Assuming the UpdateCar component is correctly rendered on a Route and receives the route props, the issue is likely that this.props.match.id won't necessarily be defined in the constructor when the component is mounting.
If you must reference props in the constructor then reference the passed props argument.
constructor(props) { // <-- reference this props
super(props)
this.state = {
carId: props.match.id, // reference props arg
brand: '',
color: ''
}
...
}
It's better to reference props directly though as it's considered anti-pattern in React to store props into local state.
componentDidMount() {
const { match } = this.props;
if (match.id) {
CarServices.getCarById(match.id)
.then((res) => {
const { brand, color } = res.data;
this.setState({ brand, color });
})
.catch(error => {
// catch and handle any Promise rejections or thrown errors
});
}
}
Don't forget to also handle the id route path parameter changing while the UpdateCar component is mounted.
componentDidUpdate(prevProps) {
const { match } = this.props;
if (prevProps.match.id !== match.id) {
CarServices.getCarById(match.id)
.then((res) => {
const { brand, color } = res.data;
this.setState({ brand, color });
})
.catch(error => {
// catch and handle any Promise rejections or thrown errors
});
}
}

Related

DB query is returning entire list instead of desired query

I am trying to query my DB for all plants posted by a specific user. My search plants by strain is working perfectly fine, returning a list of the plants with said strain datatype. My findByPostedBy is nearly identical to the findByStrain and I cannot seem to figure out the bug here, as it is simply returning the entire list of plants. Probably a silly mistake because I am a noob.
I can confirm that the username that is being sent to the DB in the get method is correct.
plant.service.js
import http from "../http-common";
class PlantDataService {
getAll() {
return http.get("/plants");
}
get(id) {
return http.get(`/plants/${id}`);
}
create(data) {
return http.post("/plants", data);
}
update(id, data) {
return http.put(`/plants/${id}`, data);
}
delete(id) {
return http.delete(`/plants/${id}`);
}
deleteAll() {
return http.delete(`/plants`);
}
findByStrain(strain) {
return http.get(`/plants?strain=${strain}`);
}
findByPostedBy(postedBy) {
return http.get(`/plants?postedBy=${postedBy}`);
}
}
export default new PlantDataService();
Plants-List.component.js
import React, { Component } from "react";
import PlantDataService from "../services/plant.service";
import { Link } from "react-router-dom";
import PlantView from "../views/PlantView";
import userProfile from "../profile/userProfile";
export default class PlantsList extends Component {
constructor(props) {
super(props);
this.onChangeSearchStrain = this.onChangeSearchStrain.bind(this);
this.retrievePlants = this.retrievePlants.bind(this);
this.refreshList = this.refreshList.bind(this);
this.setActivePlant = this.setActivePlant.bind(this);
this.removeAllPlants = this.removeAllPlants.bind(this);
this.searchStrain = this.searchStrain.bind(this);
this.searchByCurrentUser = this.searchByCurrentUser.bind(this);
this.state = {
plants: [],
userPosts: [],
currentPlant: null,
currentIndex: -1,
searchStrain: "",
currentUser: userProfile.getName()
};
}
componentDidMount() {
this.searchByCurrentUser();
}
onChangeSearchStrain(e) {
const searchStrain = e.target.value;
this.setState({
searchStrain: searchStrain
});
}
postedByCurrentUser(creatorOfPost) {
if(creatorOfPost === this.state.currentUser){
return true;
}
return false;
}
retrievePlants() {
PlantDataService.getAll()
.then(response => {
this.setState({
plants: response.data
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
refreshList() {
this.retrievePlants();
this.setState({
currentPlant: null,
currentIndex: -1
});
}
setActivePlant(plant, index) {
this.setState({
currentPlant: plant,
currentIndex: index
});
}
removeAllPlants() {
PlantDataService.deleteAll()
.then(response => {
console.log(response.data);
this.refreshList();
})
.catch(e => {
console.log(e);
});
}
searchStrain() {
PlantDataService.findByStrain(this.state.searchStrain)
.then(response => {
this.setState({
plants: response.data
});
console.log(response.data);
console.log("Plants");
console.log(this.state.plants);
})
.catch(e => {
console.log(e);
});
}
searchByCurrentUser() {
console.log(this.state.currentUser);
PlantDataService.findByPostedBy(this.state.currentUser)
.then(response => {
this.setState({
plants: response.data
});
console.log(response.data);
console.log("Plants");
console.log(this.state.plants);
console.log(this.state.currentUser);
})
.catch(e => {
console.log(e);
});
}
render() {
const { searchStrain, plants, userPosts, currentPlant, currentIndex } = this.state;
return (
<div>
<div className="list row">
<div className="col-md-8">
<div className="input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Search by strain"
value={searchStrain}
onChange={this.onChangeSearchStrain}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={this.searchStrain}
>
Search
</button>
</div>
</div>
</div>
<div className="col-md-6">
<h4>Plants List</h4>
<ul className="list-group">
{
plants &&
plants.map((plant, index) => (
<li
className={
"list-group-item " +
(index === currentIndex ? "active" : "")
}
onClick={() => this.setActivePlant(plant, index)}
key={index}
>
{plant.strain}
</li>
))}
</ul>
<button
className="m-3 btn btn-sm btn-danger"
onClick={this.removeAllPlants}
>
Remove All
</button>
</div>
<div className="col-md-6">
{currentPlant ? (
<div>
<h4>Plant</h4>
<div>
<label>
<strong>Strain:</strong>
</label>{" "}
{currentPlant.title}
</div>
<div>
<label>
<strong>Date Planted:</strong>
</label>{" "}
{currentPlant.datePlanted}
</div>
<div>
<label>
<strong>Sex:</strong>
</label>{" "}
{currentPlant.sex}
</div>
<div>
<label>
<strong>Class:</strong>
</label>{" "}
{currentPlant.class}
</div>
<div>
<label>
<strong>Posted By:</strong>
</label>{" "}
{currentPlant.postedBy}
</div>
<Link
to={"/plants/" + currentPlant.id}
className="badge badge-warning"
>
Edit
</Link>
</div>
) : (
<div>
<br />
<p>Please click on a Plant...</p>
</div>
)}
</div>
</div>
</div>
);
}
}
This could be a backend issue have you checked if the API is working using an HTTP client like Postman.
Further, I don't think it's a good idea to query the plants created by the user's name as it can cause collisions. Use user id for it.
If you have a table named users with data
id, name, created_at, updated_at
123, xyz, --, , --
Store it like
id, plant, ..., created_by
124, abc, ..., 123
Then you can query using user's id.

How I can add multiple same fields form in reactJS?

I want to add multiple persons dynamically in my form. Like I have Person 1 username and email then when I click Add Person it should make same fields for person 2 on the same page. When I click the Submit button it should give me the object of all persons.
App.js
import './App.css';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class App extends Component {
state = {
fields:[]
};
addPerson() {
this.setState({fields:[...this.state.fields, ""]})
};
handleChange(e, index) {
this.state.fields[index] = e.target.value;
this.setState({fields: this.state.fields});
}
handleSubmit(e) {
console.log(this.state,"$$")
}
render() {
return (
<div className="App">
<header className="App-header">
<div>
<h1>The Form</h1>
{
this.state.fields.map((field, index) => {
return(
<div key={index}>
<input onChange={(e)=>this.handleChange(e, index)} value={field}/>
</div>
)
}
)
}
<button onClick={(e) => this.addPerson(e)}>Add Person</button>
<button onClick={(e) => this.handleSubmit(e)}>Submit</button>
</div>
</header>
</div>
)
}
}
I want my state would be like this...
state = {
fields:[
{
id: 1,
name: 'Max',
email: 'max.max#max.in'
}
]
};
Demo of my current page.
This is my solution codesandbox
You need to have two inputs, for email and name, and depending on which input is updated, update the value of person in array.
import React, { Component } from "react";
import "./styles.css";
export default class App extends Component {
state = {
fields: []
};
addPerson() {
const newPerson = {
id: Math.random(),
name: "",
email: ""
};
this.setState({ fields: [...this.state.fields, newPerson] });
}
handleChange(e, index) {
const fieldsCopy = [...this.state.fields];
fieldsCopy.forEach(item => {
if (item.id === index) {
item[e.target.name] = e.target.value;
}
});
this.setState({ fields: fieldsCopy }, () => console.log(this.state.fields));
}
handleSubmit(e) {
console.log(this.state, "$$");
}
render() {
return (
<div className="App">
<header className="App-header">
<div>
<h1>The Form</h1>
{this.state.fields.map(field => {
return (
<div key={field.id}>
<input
onChange={e => this.handleChange(e, field.id)}
name="name"
/>
<input
onChange={e => this.handleChange(e, field.id)}
name="email"
/>
</div>
);
})}
<button onClick={e => this.addPerson(e)}>Add Person</button>
<button onClick={e => this.handleSubmit(e)}>Submit</button>
</div>
</header>
</div>
);
}
}
Edited:
Here is my version of it:
import './App.css';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class App extends Component {
index = 0;
state = {
fields: []
};
handleChange(e, idx) {
const { name, value } = e.target;
this.setState(state => {
return state.fields[idx][name] = value;
});
}
addPerson = () => {
const person = { id: this.index, name: '', email: '' };
this.index++;
this.setState({ fields: [ ...this.state.fields, person ] })
}
handleSubmit = () => {
console.log(this.state.fields);
}
render() {
const { fields } = this.state;
return (
<div className="App">
<header className="App-header">
<div>
<h1>The Form</h1>
{fields.length
? fields.map((field, idx) => (
<div key={idx}>
<label>Name:</label>
<input type="text" onChange={(e)=>this.handleChange(e, idx)} name="name" value={field.name}/>
<label>Email:</label>
<input type="email" onChange={(e)=>this.handleChange(e, idx)} name="email" value={field.email}/>
</div>
))
: null
}
<button onClick={this.handleSubmit}>Submit</button>
<button onClick={() => this.addPerson()}>Add Person</button>
</div>
</header>
</div>
)
}
}
If you are using the person id as unique identifier outside this component's state, I would suggest using some id generator library like uuid.
I hope this helps!

Setting parent state after child rendered in React

I am working on the library project. Users can add books to the library.
So, I have created the form to add a book. The form contains from the name, author, publisher, pages, ISBN and info fields. I have created the dropdown component for authors and publishers, so the user can choose from this component:
import AuthorsService from './AuthorsService'
const authorsService = new AuthorsService();
class AuthorsDropDown extends Component {
constructor(props) {
super(props);
this.state = {
authors: [],
};
}
componentDidMount() {
var self = this;
authorsService.getAuthors().then(function (result) {
self.setState({ authors: result});
});
}
render() {
return (
<div className="form-group col-sm-4">
<label>Author:</label>
<select className="form-control" onChange={(ev) => this.props.onChange(ev.target.value)}>
{this.state.authors.map( a =>
<option key={a.id} value={a.id}>{a.first_name + ' '+a.last_name }
</option>)
}
</select>
</div>
);
}
}
export default AuthorsDropDown;
I have assigned initial value for author.id and publisher.id fields in parent component as null, but, these fields only got their values after dropdown changes (i.e after onChange is fired). I have no idea how to set the value to them on rendering (i.e. initialization state). Here is the parent component:
import React, { Component } from "react";
import BookService from "./BooksService";
import AuthorsDropDown from "./AuthorsDropDown";
import PublishersDropDown from "./PublishersDropDown";
const bookService = new BookService();
class BookCreateUpdate extends Component {
constructor(props) {
super(props);
this.state = {
author:{id:null},
publisher:{id:null}
}
this.handleSubmit = this.handleSubmit.bind(this);
this.onChangeAuthor = this.onChangeAuthor.bind(this);
this.onChangePublisher = this.onChangePublisher.bind(this);
}
onChangeAuthor(new_author_id){
this.setState({author:{id:new_author_id}});
}
onChangePublisher(new_publisher_id){
this.setState({publisher:{id:new_publisher_id}});
}
handleCreate() {
alert(this.state.author.id);
bookService
.createBook({
name: this.refs.name.value,
author: this.state.author,
publisher: this.state.publisher,
page: this.refs.pages.value,
inventor_number: this.refs.inventor_number.value,
description: this.refs.description.value
})
.then(result => {
alert("The book is added!");
})
.catch(() => {
alert("Error!!");
});
}
handleUpdate(pk) {
bookService
.updateBook({
pk: pk,
name: this.refs.name.value,
author: this.refs.author,
publisher: this.refs.publisher,
pages: this.refs.pages.value,
description: this.refs.description.value
})
.then(result => {
console.log(result);
alert("Success");
})
.catch(() => {
alert("Error.");
});
}
handleSubmit(event) {
const {
match: { params }
} = this.props;
if (params && params.pk) {
this.handleUpdate(params.pk);
} else {
this.handleCreate();
}
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="row">
<div className="form-group col-sm-8">
<label>Name:</label>
<input
className="form-control"
type="text"
ref="name"/>
</div>
</div>
<div className="row">
<AuthorsDropDown onChange={this.onChangeAuthor}/>
<PublishersDropDown onChange={this.onChangePublisher}/>
</div>
<div className="row">
<div className="form-group col-sm-4">
<label>Pages:</label>
<input
className="form-control"
type="number"
ref="pages"/>
</div>
<div className="form-group col-sm-4">
<label>ISBN:</label>
<input
className="form-control"
type="text"
ref="inventor_number"/>
</div>
</div>
<div className="row">
<div className="form-group col-sm-4">
<label>Info:</label>
<textarea
className="form-control"
ref="description"/>
</div>
</div>
<input className="btn btn-primary" type="submit" value="ok"/>
</form>
);
}
}
export default BookCreateUpdate;
I think you should consider a different way of tackling this problem. If I understand your requirement this component both creates and updates books. In this case, the <BookCreateUpdate/> component should receive a property which is the target book. For creation, it should be an empty object. For an update, it should be the object to update.
I think the mentioned concern relates to when editing. I suppose that books are persisted somewhere. If a book is passed in edit mode then the initial value should be passed down to the child components (input, AuthorsDropDown, PublishersDropDown) from the parent (<BookCreateUpdate/>).
class BookCreateUpdate extends Component {
constructor(props) {
super(props);
this.state(this.props.book)
}
onInputChange = propName => (e) => {
this.setState({[propName]: e.target.value })
}
...
handleCreate() {
const bookDraft = this.state;
bookService
.createBook(bookDraft)
.then(result => {
alert("The book is added!");
})
.catch(() => {
alert("Error!!");
});
}
...
render(){
const bookDraft = this.state;
return (
...
<div className="row">
<div className="form-group col-sm-8">
<label>Name:</label>
<input
className="form-control"
type="text"
value = {bookDraft.name}
onChange={this.onInputChange('name')}
/>
</div>
</div>
<AuthorsDropDown onChange={this.onChangeAuthor} authorId = {bookDraft.authorId}/>
<PublishersDropDown onChange={this.onChangePublisher} publisherId = {bookDraft.publisherId}/>
....
)
}
}
BookCreateUpdate.propsTypes = {
book: PropTypes.object
}
BookCreateUpdate.defaultProp = {
book: {authorId: null, publisherId: null}
}
It is also best not to use refs in this case. It is cleaner to pass a value to input and pass a callback for onChange event.

React how to update a status when I receiving a props from a wrapper component - REACT JS

I'm learning react and also I really want to solve this problem. When I click on Edit on my component Tabela.js, I send an action to App.js and from that App.js I send an Id code to Cadastrar.js. But when I call the function to change de name of the button Registration to Update, it shows me an error message like that:
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
What should I do to solve it?
App.js
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Cadastrar from "./components/Cadastrar";
import Tabela from "./components/Tabela";
class App extends Component {
state = {
update: '',
idTamanhoEditar: ''
}
editarRegistro = (idRegistroEditarTabela) => {
this.setState({idTamanhoEditar: idRegistroEditarTabela})
}
updateTabela = (atualizarTabela) => {
this.setState({update: atualizarTabela})
}
render() {
return(
<div>
<Cadastrar atualizarTabela={this.updateTabela} editarFromParent={this.state.idTamanhoEditar}/>
<Tabela editarRegistro={this.editarRegistro} updateFromParent={this.state.update} />
</div>
)
}
}
export default App;
Cadastrar.js
import React, { Component } from 'react';
import './Cadastrar.css';
import axios from "axios";
class Cadastrar extends Component {
constructor(props) {
super(props);
this.state = {
tamanho: {
id: '',
descricao: '',
},
error: '',
sucess: '',
tipoAcao: 'Cadastrar'
};
this.atualizaDados = this.atualizaDados.bind(this);
this.cadastrar = this.cadastrar.bind(this);
this.editarCadastro = this.editarCadastro.bind(this);
}
atualizaDados(e) {
let tamanho = this.state.tamanho;
tamanho[e.target.name] = e.target.value;
this.setState({tamanho: tamanho});
console.log(e);
}
cadastrar(e) {
const {tamanho} = this.state;
if(tamanho.descricao !== '') {
axios.post(`http://localhost/react-project/src/api/register.php`, { descricao: tamanho.descricao })
.then(res => {
if(res.data === 'sucess') {
this.setState({tamanho:{id:'', descricao: ''}})
//Tabela.atualizarItensTabela();
this.setState({sucess: 'Cadastro efetuado com sucesso!', error: ''})
this.props.atualizarTabela(true);
}
})
} else {
this.setState({error: 'Preencha o campo descrição!', sucess: ''})
}
e.preventDefault();
}
editarCadastro(idTamanhoEditar) {
console.log(idTamanhoEditar);
if(this.state.tamanho.id !== idTamanhoEditar) {
this.setState({
tipoAcao: 'Atualizar',
tamanho:{id: idTamanhoEditar}
});
}
}
render() {
return (
<div id='formulario-de-cadastro' className='container'>
<div className='page-header'>
<h2 className='titulo-cadastrar-tamanho'>Cadastrar Tamanho</h2>
</div>
<form onSubmit={this.cadastrar}>
<input type='hidden' name='id' value={this.state.tamanho.id} onChange={ this.atualizaDados } /><br/>
<div className='form-group'>
<label htmlFor='descricao'>Descrição</label>
<input type='text' className='form-control' name='descricao' id='descricao' value={this.state.tamanho.descricao} onChange={ this.atualizaDados } /><br/>
<button type='submit' className='btn btn-primary'>{this.state.tipoAcao}</button>
<button className='btn btn-danger ml-1'>Cancelar</button>
</div>
</form>
{this.state.error && <p className='alert alert-warning'>{this.state.error}</p>}
{this.state.sucess && <p className='alert alert-success'>{this.state.sucess}</p>}
{this.props.editarFromParent && this.editarCadastro(this.props.editarFromParent)}
</div>
);
}
}
export default Cadastrar;
Tabela.js
import React, { Component } from 'react';
import axios from 'axios';
import './Tabela.css';
class Tabela extends Component {
constructor(props) {
super(props);
this.state = {
tamanhos: [],
tamanho: {
id: '',
descricao: ''
},
}
this.apagarTamanho = this.apagarTamanho.bind(this);
this.atualizarItensTabela = this.atualizarItensTabela.bind(this);
}
componentDidMount() {
this.atualizarItensTabela();
}
atualizarItensTabela() {
let url = 'http://localhost/react-project/src/api/consult.php';
fetch(url)
.then((r) => r.json())
.then((json) => {
this.setState({tamanhos: json});
});
}
apagarTamanho(e, idTamanho) {
e.preventDefault();
axios.post(`http://localhost/react-project/src/api/delete.php`, { id: idTamanho })
.then(res => {
if(res.data === 'sucess') {
this.atualizarItensTabela();
console.log('registro apagado com sucesso');
}
})
}
editarTamanho(e, idTamanho) {
this.props.editarRegistro(idTamanho);
e.preventDefault();
}
render() {
return (
<div className='container mt-5'>
{this.props.updateFromParent && this.atualizarItensTabela()}
<table id='tabela-tamanhos' className='table table-hover'>
<thead>
<tr>
<th scope="col">Código</th>
<th scope="col">Descrição</th>
<th scope="col">Ações</th>
</tr>
</thead>
<tbody>
{this.state.tamanhos.map(
tamanho=>
<tr key={tamanho.id} className='row-tamanho'>
<th scope="row">{tamanho.id}</th>
<td>{tamanho.descricao}</td>
<td>
<button className='btn btn-primary mr-1' onClick={(e)=>this.editarTamanho(e, tamanho.id)}>Editar</button>
<button className='btn btn-danger' onClick={(e)=>this.apagarTamanho(e, tamanho.id)}>Apagar</button>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
export default Tabela;
In Cadastrar.js you are calling setState during render {this.props.editarFromParent && this.editarCadastro(this.props.editarFromParent)}
editarCadastro has a call to setState
editarCadastro(idTamanhoEditar) {
console.log(idTamanhoEditar);
if(this.state.tamanho.id !== idTamanhoEditar) {
this.setState({
tipoAcao: 'Atualizar',
tamanho:{id: idTamanhoEditar}
});
}
}
You cannot update state during render. If you need to update state from props you should use getDerivedStateFromProps

Reactjs: Warning: A component is changing a controlled

I am writing a react crud app, my crud is working nice but it has an console error and below this:
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. More info:
I tried a lot reading too many thing on stackoverflow, can anyone help me please?
this is my home.js file:
import React from "react"
import Table from "./table"
import Form from "./form"
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 'SAVE', // button name
employees: [{name: 'jhon', age: '23', email: 'a#a'}, {name: 'doe', age: '24', email: 'b#a'}],
currentEmp: {},
isFormVisible: false
};
this.onSubmit = this.onSubmit.bind(this);
this.onDelete = this.onDelete.bind(this);
this.setIndex = this.setIndex.bind(this);
}
onSubmit(name, age, email, index=null) {
if(!index && this.state.current == 'SAVE'){
this.setState({ employees: [...this.state.employees, { name: name, age: age, email: email }] });
}
else if(this.state.current == 'Update'){
var emp = this.state.employees;
emp[this.state.index].name = name; //use index from state
emp[this.state.index].age = age;
emp[this.state.index].email = email;
this.setState({
currentEmp: {},
employees: emp,
current: 'SAVE'
});
}
else{
this.setState({
currentEmp: {},
current: 'SAVE',
});
}
};
setIndex(index){
var emp = this.state.employees[index];
emp.index = index;
this.setState({
currentEmp: emp,
current: 'Update',
index //set index in state
});
}
// delete employee
onDelete(event, index) {
this.setState({
employees: this.state.employees.filter((item, itemIndex) => (index != itemIndex)),
});
};
render() {
return (
<React.Fragment>
<h1>Employee Information System</h1>
{this.state.isFormVisible && <div>
<Form
currentEmp={this.state.currentEmp}
submitMe={this.onSubmit}
currentButtonName={this.state.current} />
</div>
}
<button onClick={() => this.setState({isFormVisible: true})}>ADD NEW</button>
<hr/>
<table className="table table-striped table-dark">
<Table onUpdateTry={this.edit} editThis={this.setIndex} employees={this.state.employees} deleteMe={this.onDelete} />
</table>
<p className="test">Ignore this please ! Just showed if sass works or not</p>
</React.Fragment>
);
}
}
export default Home;
and this is my form.js file
import React, { Fragment } from "react"
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {name: '', age: '', email: ''};
this.onHandleChange = this.onHandleChange.bind(this);
this.submit = this.submit.bind(this);
}
submit(event, name, age, email) {
if (this.props.submitMe) {
this.props.submitMe(name, age, email);
}
this.setState({name: '', age: '', email: ''}); // clear form after click on submit
}
onHandleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
componentDidUpdate(prevProps){
if(prevProps.currentEmp != this.props.currentEmp){
this.setState({
index: this.props.currentEmp.index,
name: this.props.currentEmp.name,
age: this.props.currentEmp.age,
email: this.props.currentEmp.email,
});
}
}
render() {
return (
<form>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.name} name="name" type="text" />
</div>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.age} name="age" type="number"/>
</div>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.email} name="email" type="text"/>
</div>
<button onClick={(event) => this.submit(event, this.state.name, this.state.age, this.state.email)} type="button">{this.props.currentButtonName}</button>
<button onClick={() => this.setState({isFormVisible: false})}>HIDE ME</button>
</form>
);
}
}
export default Form;
and this is my table.js file:
import React, {Fragment} from "react"
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
employees: this.props.employees
};
//this.onDelete = this.onDelete.bind(this);
this.onEdit = this.onEdit.bind(this);
}
onEdit(event, index){
if(this.props.editThis){
this.props.editThis(index);
}
}
render() {
return (
<Fragment>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
<th scope="col">Email</th>
<th scope="col">EDIT</th>
<th scope="col">DELETE</th>
</tr>
</thead>
<tbody>
{this.props.employees.map((item, index) => (
<tr key={index}>
<td>{item.name}</td>
<td>{item.age}</td>
<td>{item.email}</td>
<td>
<button
type="button"
onClick={(event) => this.onEdit(event, index)}
className="btn btn-primary btn-sm">EDIT
</button>
</td>
<td>
<button
onClick={(event) => this.props.deleteMe(event, index)}
type="button" className="btn btn-danger btn-sm">DELETE
</button>
</td>
</tr>
))}
</tbody>
</Fragment>
);
}
}
export default Table;
The error occurs only when i add something or click on SAVE buttion or update button. Can anyone help me in this case?
Your problem is with this,
currentEmp: {}
You are setting currentEmp to blank object, and in Form component using this object to set state in componentDidUpdate, in result state in Form component is not getting values.
Also don't mutate state directly.
You can set your currentEmp to empty value object, and your state updation should be,
this.setState({
employees: this.state.employees.map((emp,index) => index === this.state.index ? {name,age,email} : emp),
current: 'SAVE',
currentEmp:{name:'',age:'',email:''}
});
Also in your Form component, in submit function you are doing this,
this.setState({name: '', age: '', email: ''});
which is not needed when you are setting currentEmp:{name:'',age:'',email:''}. Your componentDidUpdate method will take care of this.
Demo
Your problem is in your onSubmit method. You are resetting your currentEmp to {} and using it in your Form component. So, when you reset it affects your Form component and all the values become null there. So, you can skip this step maybe?
this.setState({
employees: emp,
current: "SAVE"
});
Also, I couldn't look so carefully but probably you are mutating your state directly in many places. For example in the update part.
var emp = this.state.employees;
emp[this.state.index].name = name; //use index from state
This is a mutation. Assigning an object to a new one just do this by reference. So, when you change a property in the new one then it changes the original one. Maybe something like that works:
const emp = this.state.employees.map((el, i) => {
const { index } = this.state;
const curEmp = this.state.employees[index];
if (i !== index) return el;
return { ...curEmp, name, age, email };
})
Have you tried to read properties in the submit function?
Like:
submit() {
const { name, age, email } = this.state;
if (this.props.submitMe) {
this.props.submitMe(name, age, email);
}
this.setState({name: '', age: '', email: ''}); // clear form after click on submit
}

Categories

Resources