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
}
Related
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
});
}
}
In the below code, I am trying to assign the value of ParentId in { ParentId: 'ou-wmno-yeeol4ok' } from the implemented dropdown select instead of getting it hardcoded, but I don't know how to pass this.state.value in componentDidMount() function.
Interestingly if I try to do something like this { ParentId: this.state.value } it doesn't work.
Can someone please take a look and help me out. Thanks.
import React, {Component} from 'react'
import axios from '../../axios'
export default class users extends Component {
constructor(props) {
super(props);
this.state = {
Users: [],
value: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
//alert('Your favorite flavor is: ' + this.state.value);
console.log( this.state.value);
event.preventDefault();
}
componentDidMount(){
axios
.post(`/`, { ParentId: 'ou-wmno-yeeol4ok' })
.then(res => {
const data = res.data
const valuesArray = JSON.parse(data)
console.log(valuesArray)
const users = valuesArray.Accounts.map(u =>
<tr key={u.Id}>
<td>{u.Name}</td>
<td>{u.Arn}</td>
<td>{u.Id}</td>
<td>{u.Email}</td>
<td>{u.JoinedMethod}</td>
<td>{u.JoinedTimestamp}</td>
<td>{u.Status}</td>
</tr>
)
this.setState({
users
})
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<h1 id='awsorg'>AWS Accounts in Organization</h1>
<div>
<label>
Select AWS Organization
<select id="dropdown" value={this.state.value} onChange={this.handleChange}>
<option value="test-1">test-1</option>
<option value="ou-wmno-yeeol4ok">Suspended</option>
<option value="test-3">test-3</option>
<option value="test-4">test-4</option>
</select>
<input type="submit" value="Submit" />
</label>
</div>
<table id='accounts'>
<tbody>
<tr>
<th>Name</th>
<th>Arn</th>
<th>id</th>
<th>Email</th>
<th>JoinedMethod</th>
<th>JoinedTimestamp</th>
<th>Status</th>
</tr>
{this.state.users}
</tbody>
</table>
</div>
</form>
)
}
}
{ ParentId: 'this.state.value' }
not works because the this.state.value surrounded with this char '
try this
componentDidMount(){
let parentId = this.state.value
axios.post(`/`, { ParentId: parentId })
... rest of the code ..
Newbie issues: Building a shopping cart, I have a form in a modal.
The submit button is that of the modal (outside the form tags), but seems to be sending the form to the handleOnSubmit handler. However, apart from closing the modal, I am unable to update the productBought and productName state values.
Ideally, I would like to push the basket[] content (from InputList class) unto a new array in the parent (AsyncAwaitLocalhost), after which I can submit the entire purchase to an API.
refs are deprecated and various other flavors of sharing data are failing me.
My challenges are:
(i) How can I share the push the basket object to a waiting parent state object?
and
(ii) How can I submit the form after console logging the form objects?
Thanks.
AsyncAwaitLocalhost.jsx
import React from 'react'
import "./App.css"
import { Modal, Button } from "react-bootstrap"
import Form from "./Form.jsx"
class AsyncAwaitLocalhost extends React.Component {
constructor(props) {
super(props);
this.state = {
sharedVar: "init",
jsonContent: [],
isOpen: null,
productBought: 0,
productName: "anItem"
}
this.handleOnSubmit = this.handleOnSubmit.bind(this)
}
updateShared(shared_value) {
this.setState({ sharedVar: shared_value });
}
async componentDidMount() {
// GET request using fetch with async/await
const response = await fetch('http://localhost:8080/cakes');
const data = await response.json();
this.setState({ jsonContent: data.cakes, greeting: this.state.greeting, newGreeting: this.state.newGreeting })
}
openModal = (index) => () => this.setState({ isOpen: index });
closeModal = () => this.setState({ isOpen: null });
handleCloseModal() {
this.setState({ isOpen: null });
}
handleOnSubmit(event) {
event.preventDefault()
this.setState({ productBought: event.target.name.value })
this.setState({ productName: event.target.name.value })
console.log("Inside handleOnSubmit: " + this.state.productBought)
this.handleCloseModal();
}
render() {
const { jsonContent } = this.state;
const { sharedVar } = this.state;
const { productBought } = this.state;
const { productName } = this.state;
console.log("shared var in parent: " + sharedVar)
console.log("productBought in parent: " + productBought)
const Cake = ({ name, id, text, image, comment, price, dbutton, dmodal }) => (
<div className="rounded-well">
<div>
<img src={image} width="120px" alt={name} />
</div>
<div>
<p>{name}</p>
<p>{text}</p>
<p>{comment}</p>
<p>{id}</p>
<p>{price}</p>
<div> {this.state.sharedVar}</div>
<div>{dbutton}</div>
<div id="dModal" className="dModalClosed">{dmodal}</div>
</div>
</div>
);
return (
<div className="card text-center m-3">
Showing HTTP Requests, CSS , Data Transfer & Modals
<h5 className="card-header">See Cakes</h5>
<div className="card-body">
<div>
{this.state.jsonContent.map((cake, index) => (
<Cake
image={cake.image}
text={cake.text}
key={index}
price={"£" + cake.price + ".00"}
dbutton={<Button onClick={this.openModal(index)}>See more</Button>}
dmodal={
<Modal id={cake.name} show={this.state.isOpen === index}
onHide={this.closeModal} className="image-container">
<Modal.Header closeButton>
<Modal.Title>{cake.text}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
<img src={cake.image} width="300px" alt={cake.name} />
</div>
<br />
<p>{cake.text}</p>
<p>{cake.comment}</p>
<div>
<form id={cake.name} method="POST" action="#" onSubmit={this.handleOnSubmit} >
{<Form dataFromParent={this.state.jsonContent[index]} onChange />}
</form>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={this.closeModal}>
Cancel
</Button>
<Button form={cake.name} type="submit" variant="secondary" onClick={this.handleOnSubmit}>
Add to basket
</Button>
</Modal.Footer>
</Modal>
}
/>
))}
<div>
</div>
</div>
</div>
</div>
);
}
}
export { AsyncAwaitLocalhost };
Form.jsx
import React from 'react';
class InputList extends React.Component {
constructor(props) {
super(props)
this.state = {
bought: 0,
sharedVar: "input field",
cakeItems:{},
basket: [],
input: 0,
item: ""
}
}
handleChange(index, e) {
const value = e.target.value
this.props.onChange(index, {...this.props.data[index], buy: value})
this.state.bought = value
this.state.cakeItems = {item: this.props.data[0].name, number: this.state.bought}
this.state.basket.push(this.state.cakeItems)
}
render() {
const { bought } = this.state;
const {cakeItems} = this.state;
const {basket} = this.state;
const { sharedVar } = this.state;
const { item } = this.state;
// this.state.sharedVar = this.props.dataFromParent[0].name
console.log("pre-bought: " + bought);
console.log("Shared var in input class: " + sharedVar);
console.log("basket in InputClass: " + JSON.stringify(basket))
return (
<div>
Buy:
{this.props.data.map((d, i) => <input key="0" value={d.buy} name="productBought" onChange={this.handleChange.bind(this, i)} />)}
{this.props.data.map((d, i) => <input key="1" type="hidden" value={d.name} name="productName" />)}
<>
To buy: {bought}
</>
</div>
)
}
}
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [],
basket: [],
sharedVar: "form field",
usrBasket: {
buy: 1,
id: "randomStr",
items: [{buy: 1, item: ""}],
value_key: "",
nuArry: []
},
input: {}
}
}
loadStuff() {
this.setState({
data: [this.props.dataFromParent]
})
// console.log('got some stuff from the server in Form: ' + JSON.stringify(this.props.dataFromParent))
}
handleChange(index, value) {
console.log('data changed!', value)
const data = [...this.state.data]
data.splice(index, 1, value)
this.setState({
data,
})
this.state.basket = this.state.cakeItems
console.log("basket in FormClass: " + JSON.stringify(this.state.basket))
}
componentDidMount() {
this.loadStuff()
}
render() {
const {basket} = this.state;
const { value_key } = this.state;
this.state.value_key = "new vk";
console.log("value_key in Form: " + value_key);
const { sharedVar } = this.state;
this.state.sharedVar = "Hello greeting";
console.log("sharedVar in Form: " + sharedVar);
return (
<div>
<InputList data={this.state.data} onChange={this.handleChange.bind(this)} />
</div>
);
}
}
export default Form```
<br>
Haa, the newbie life...!
So after more study and getting to learn the entire framework better the solution is thus.
(i) How can I share the push the basket object to a waiting parent state object?
Create classes - (some people argue for functional components) that are distinct and do the one thing well. The Form component renders the modal, collects the form values and submits those values to the parent through props.
In Parent, make ready to receive child props
<Cakes onSubmitChild={this.getFormValues} />
Then send the values to the parent component from Child like this:
this.props.onSubmitChild(this.state.productField)
(ii) How can I submit the form after console logging the form objects?
The Parent has the submit to server button. After the Child has submitted the form values to the Parent, it is left to create a JSON object and submit it to the server using AJAX (or whatever the coder prefers).
Note that there will be CORS issues to iron out on the server side to resolve this, but I recommend the cors library in npm.
I have one question. I created contact form in react and I want to collapsed only single contact, which was clicked. Toggle is method which should collapse it. And colapse is state. My problem is that when I click it affect all contact and all are collapsed. How can I improve it?
ContactBook.js
import React, { Component } from "react";
import Contact from "../Contact/Contact";
import "./ContactBook.css";
class ContactBook extends Component{
constructor(props){
super(props);
this.state = {
colapse :true,
contacts: [
{
id: 1,
name: 'Propulsion Academy',
address: 'Zurich',
avatar: 'propulsion-academy-logo.png'
},
{
id: 2,
name: 'Propulsion Academy',
address: 'Luzern',
avatar: 'propulsion-academy-logo.png'
},
{
id: 3,
name: 'Propulsion Academy',
address: 'Munich',
avatar: 'propulsion-academy-logo.png'
},
],
};
}
toggle=()=>{
const doesShow = this.state.colapse;
this.setState({colapse: !doesShow});
}
deleteContact=(contactIndex)=>{
//with slice method we create copy of an array
const contacts =this.state.contacts.slice();
contacts.splice(contactIndex, 1);
this.setState({contacts: contacts})
}
//get name from input
addName = e =>{
this.setState({
name: e.target.value,
})
}
//get address from input
addAddress = e =>{
this.setState({
address: e.target.value,
})
}
//update state on button click
handleSubmit = (e) =>{
e.preventDefault()
if(this.state.name && this.state.address) {
this.setState(state =>{
const newContact = {
id: Math.max(...state.contacts.map(c => c.id))+1,
name: this.state.name,
address: this.state.address,
}
return{
contacts:[...state.contacts, newContact]
}
})
}
}
render() {
return (
<div className="contactBook">
<form className ="addContact" >
<p>New Contact</p>
<label id="name"><p>Name</p><input type='text' id="name" onChange={this.addName}/></label>
<label id="address"><p>Address:</p><input type='text' id="address" onChange={this.addAddress} /></label>
<input type='file' name='file' />
<button type='submit' onClick= {this.handleSubmit}>SUBMIT</button>
</form>
<div className="contacts">
{this.state.contacts.map((contact, index) =>
< Contact key={contact.id} contact={contact} delete={()=>this.deleteContact(index)} colapse={this.state.colapse} toggle={this.toggle}/>)
}
</div>
</div>
);
}
};
export default ContactBook;
Contact.js
import React from "react";
import "./Contact.css";
import avatar from '../assets/user.png'
const Contact = (props) =>{
return (
<div className = "col" >
<img src={avatar} alt="avatar" onClick={props.toggle}/>
{props.colapse === true ?
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div> : null
}
</div>
)
};
export default Contact;
I recommend to you to move the collapse and his method to the Contact component it self like this :
const Contact = (props) =>{
[collapse,setCollapse] = useState(true)
return (
<div className = "col" >
<img src={avatar} alt="avatar" onClick{()=>setCollape(prev=>!prev)}/>
{collapse === true ?
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div> : null
}
</div>
)
};
In this component I created a state that will manage the collapse for each of the component the render in the map.
further more, the prev give you the last value you submit and it's best practice to use the prev instead of just setCollapse(!collapse)
You have one function for all contacts, and since you use .map() they will all behave the same, since toggling one toggles the state which is used to render all individual contacts. The solution would be to pass the selected contact in your state so your app actually knows which one is to be rendered! Hopefully that makes sense!
Good luck and let us know how things work out!
This is it:
ContactBook.js
import React, { Component } from "react";
import Contact from "./Contact";
// import "./ContactBook.css";
class ContactBook extends Component {
constructor(props) {
super(props);
this.state = {
contacts: [
{
id: 1,
name: "Propulsion Academy",
address: "Zurich",
avatar: "propulsion-academy-logo.png",
colapse: true
},
{
id: 2,
name: "Propulsion Academy",
address: "Luzern",
avatar: "propulsion-academy-logo.png",
colapse: true
},
{
id: 3,
name: "Propulsion Academy",
address: "Munich",
avatar: "propulsion-academy-logo.png",
colapse: true
}
]
};
}
// toggle = () => {
// const doesShow = this.state.colapse;
// this.setState({ colapse: !doesShow });
// };
deleteContact = contactIndex => {
//with slice method we create copy of an array
const contacts = this.state.contacts.slice();
contacts.splice(contactIndex, 1);
this.setState({ contacts: contacts });
};
togglecontact = contactIndex => {
let contacts = this.state.contacts.slice();
contacts[contactIndex].colapse = !contacts[contactIndex].colapse;
this.setState({ contacts: contacts });
};
//get name from input
addName = e => {
this.setState({
name: e.target.value
});
};
//get address from input
addAddress = e => {
this.setState({
address: e.target.value
});
};
//update state on button click
handleSubmit = e => {
e.preventDefault();
if (this.state.name && this.state.address) {
this.setState(state => {
const newContact = {
id: Math.max(...state.contacts.map(c => c.id)) + 1,
name: this.state.name,
address: this.state.address
};
return {
contacts: [...state.contacts, newContact]
};
});
}
};
render() {
return (
<div className="contactBook">
<form className="addContact">
<p>New Contact</p>
<label id="name">
<p>Name</p>
<input type="text" id="name" onChange={this.addName} />
</label>
<label id="address">
<p>Address:</p>
<input type="text" id="address" onChange={this.addAddress} />
</label>
<input type="file" name="file" />
<button type="submit" onClick={this.handleSubmit}>
SUBMIT
</button>
</form>
<div className="contacts">
{this.state.contacts.map((contact, index) => (
<Contact
key={contact.id}
contact={contact}
togglecontact={() => this.togglecontact(index)}
delete={() => this.deleteContact(index)}
colapse={this.state.colapse}
toggle={this.toggle}
/>
))}
</div>
</div>
);
}
}
export default ContactBook;
contactbook.js
import React from "react";
// import "./Contact.css";
const Contact = props => {
let buffer;
props.contact.colapse === true
? (buffer = (
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div>
))
: null;
return (
<div className="col">
<img
onClick={props.togglecontact}
src="data:image/jpeg;base64,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"
/>
{buffer}
</div>
);
};
export default Contact;
can check it at:https://stackblitz.com/edit/react-mrvrr1?file=src%2FContact.js
only Toggling single element.
I want a container with table that has one column "enabled" which can be toggled. I want to save the state of toggle in object I used to display the row (In the example below, I want to store it in enable attribute of student object). I have table and toggle button displaying properly. But the toggle button is not clickable/togglable and doesnt store the state. live code here.
Here is my code
class student {
constructor(id, name, age,email,enable) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.enable = enable;
}
}
const Switch = ({ isOn, handleToggle}) => {
return (
<>
<input
checked={isOn}
onChange={handleToggle}
className="react-switch-checkbox"
id={`react-switch-new`}
type="checkbox"
/>
<label
className="react-switch-label"
htmlFor={`react-switch-new`}
>
<span className={`react-switch-button`} />
</label>
</>
);
};
class Table extends React.Component {
constructor(props) {
super(props)
this.state = {
students: [ new student(1, 'foo', 12, 'foo#gmail.com', true),
new student(2, 'bar', 22, 'bar#gmail.com', false),
new student(3, 'foobar', 44, 'foo#gmail.com', true),
new student(4, 'foofoo',57, 'foofoo#gmail.com', false)
]
}
}
renderTableHeader() {
let header = Object.keys(this.state.students[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, enable } = student //destructuring
//const [value, setValue] = useState(false);
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><Switch
isOn={enable}
handleToggle={() => student.enable=!enable}
/></td>
</tr>
)
})
}
render() {
return (
<div>
<h1 id='title'>React Dynamic Table</h1>
<table id='students'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render(<Table />, document.getElementById('root'));
Here is the screenshot of how page looks
The issue is, you cannot directly change the state like,
handleToggle={() => student.enable=!enable}
This will never change your state. To change your state you must use setState.
You should have a dedicated function to toggle your state,
handleToggle = (id) => {
this.setState({
students: this.state.students.map(std => {
if(parseInt(std.id) === parseInt(id)){ //parse id to integer for safer side
return {...std, enable: !std.enable}
}else{
return std;
}
})
})
}
You should provide this function to your Switch. Also provide id for updation.
<Switch
isOn={enable}
id={id} //provide id for updation
handleToggle={this.handleToggle} //provide function reference
/>
You need to make some changes to your Switch,
const Switch = ({ isOn, handleToggle, id}) => { //take prop id
return (
<>
<input
checked={isOn}
onChange={() => handleToggle(id)} //call handleToggle using id
className="react-switch-checkbox"
id={`react-switch-new${id}`} // Make it unique by adding id
type="checkbox"
/>
<label
className="react-switch-label"
htmlFor={`react-switch-new${id}`} // Make it unique by adding id
>
<span className={`react-switch-button`} />
</label>
</>
);
};
Demo