When I hit the Edit button I trigger a flag for enabling editing. The desired behaviour would be to enable editing only on one item at the time. However, all the item get enabled for editing by the flag.
<li key={recipe._id} className="list__item recipe" id={randomString()}>
<Card body id={`card-${recipe._id}`} >
<CardTitle name={randomString()}><input type="text" name="name" defaultValue={recipe.name} readOnly={disabled} className="recipe__name" onChange={(e) => {changeItem(e, recipe)}} /></CardTitle>
<CardImg top width="100%" src={require('../assets/318x180.svg')} alt="Card image cap" />
<input id={String(recipe._id)} className="toggle" type="checkbox" onChange={(e) => {handleChange(e)}} />
<label htmlFor={String(recipe._id)} className="lbl-toggle bold">Ingredients</label>
<ul key={recipe._id} className="ingredients expand-content" id='ingredientList'>
{iselect()}
</ul>
<table>
<tbody>
<tr>
<td className={'center'}><span className={'bold'}>Actual Cost:</span></td>
<td className={'center'}><span className={'bold'}>Proposed Price:</span></td>
</tr>
<tr>
<td className={'center'}><span className="recipe__cost">£{cost(Math.floor(Math.random()*10))}</span></td>
<td className={'center'}><span className="recipe__proposed">£{proposed(cost(Math.floor(Math.random()*10)))}</span></td>
</tr>
</tbody>
</table>
<Button color="primary" onClick={() => { saveEditRecipe(recipe); }} className={`bold save-button ${disabled ? "hidden" : "show"}`}>Save</Button>
<Button color="primary" onClick={(e) => { editRecipe(e, recipe); }} id={`card-${recipe._id}`} className={'edit-button'}>Edit</Button>
<Button color="danger" onClick={() => { deleteRecipe(recipe); }} className={'delete'}>X</Button>
</Card>
</li>
// /client/src/components/Recipe.js
import React, { useState, useEffect} from "react";
import { Card, Button, CardTitle, CardText, CardImg } from 'reactstrap';
import { FaPen} from "react-icons/fa";
import './Recipe.css';
// SERVICES
import recipeService from '../services/recipeService';
import ingredientService from '../services/ingredientService';
function Recipe() {
const initialEditState = { id: null, name: '', ingredients: null }
const [recipes, setrecipes] = useState(null);
const [newRecipe, setNewRecipe] = useState({});
const [ingredients, setingredients] = useState(null);
const [fields, setFields] = useState([{name:"", quantity: null}]);
const [editFields, setEditFields] = useState([{ name: "", quantity: null }]);
const [disabled, setDisabled] = useState(true);
const [currentRecipe, setCurrentRecipe] = useState(initialEditState)
// const inputRef = useRef();
// function handleGamClick() {
// setDisabled(!disabled);
// }
useEffect(() => {
if(!recipes) {
getRecipes();
}
if(!ingredients) {
getIngredients();
}
if(currentRecipe._id !== undefined) {
setDisabled(false);
}
// console.log(disabled);
}, [recipes, ingredients, currentRecipe])
const getRecipes = async () => {
let res = await recipeService.getAll();
setrecipes(res);
}
const getIngredients = async () => {
let res = await ingredientService.getAll();
setingredients(res);
}
// const loadIngredients = (recipe) => {
// let initial = recipe.ingredients;
// let ingredients;
// const values = [...editFields];
// for (var i in initial){
// ingredients = initial[i].ingredients;
// }
// initial.map((ingredient) => {
// setEditFields(ingredient);
// }
// )
// setEditFields(values);
// console.log(values);
// }
// Method for calculating recipe cost
const cost = (nameKey, ingredient) => {
return 20;
}
// Method for calculating recipe proposed price
const proposed = (recipe) => {
return recipe * 2.5;
}
// Method for creating recipe
const createRecipe = async (recipe) => {
const ingredients = fields;
const object = {...newRecipe, ingredients }
recipeService.create(object);
window.location.reload();
}
// Method for
const changeItem = (e, recipe) => {
recipe[e.target.name] = e.target.value;
return recipe;
}
// Method for editing recipe
const editRecipe = async (e, recipe) => {
setCurrentRecipe(recipe);
// if (e.target) {
// setDisabled(false);
// }
// setEditFields(recipe.ingredients);
// const ingredients = editFields;
// const object = { recipe, ingredients }
// if (recipe._id === currentRecipe._id) {
// setDisabled(false);
// }
// console.log(editFields);
// recipeService.edit(object);
// window.location.reload();
}
const saveEditRecipe = async (recipe) => {
setCurrentRecipe(initialEditState);
setDisabled(true);
}
// Method for deleting recipe
const deleteRecipe = async (recipe) => {
if (window.confirm('Are you sure you wish to delete this item?')) {
recipeService.delete(recipe);
window.location.reload();
}
}
// Method for
const handleChange = (e) => setNewRecipe({
...newRecipe,
// [e.target.name]:e.target.name === "name" ? e.target.value : Number(e.target.value ),
[e.target.name]:e.target.value
});
// Method for
function handleChanger(idx, event) {
// console.log(recipes)
// console.log(event.target.name);
const values = event.target.className.includes("edit") ? [...editFields] : [...fields];
if (event.target.name === "name") {
values[idx].name = event.target.value;
} else {
values[idx].quantity = event.target.value;
}
event.target.className.includes("edit") ? setEditFields(values) : setFields(values);
}
// Method for
function handleAdd(e, idx) {
console.log(idx);
const values = e.target.className.includes("edit") ? [...editFields] : [...fields];
values.push({name:"", quantity: null});
e.target.className.includes("edit") ? setEditFields(values) : setFields(values);
}
// Method for
function handleRemove(e, i) {
const values = e.target.className.includes("edit") ? [...editFields] : [...fields];
values.splice(i, 1);
e.target.className.includes("edit") ? setEditFields(values) : setFields(values);
}
// rendering ingredients dropdown
const renderIngredientName = (ingredient) => {
let initial =[];
for (ingredient in ingredients) {
initial.push(ingredients[ingredient].name);
}
let createOption = (option) => {
return <option key={option.toLowerCase()} defaultValue={option.toLowerCase()} >{option.toLowerCase()}</option>;
}
return initial.map(createOption);
}
// Method for
const randomString = (recipe) => {
return String("expand"+Math.floor(Math.random()*100))
}
// Rendering recipes
const renderRecipe = recipe => {
const iselect = () => {
let initial = recipe.ingredients;
function splice(idx){
return initial.splice(idx, 1)
}
return initial.map((ingredient, idx) =>
<li key={`${ingredient}-${idx}`}>
<select name="name" className="edit recipe__ingredient__name" value={ingredient.name || ""} disabled={disabled} onChange={(e) => {handleChanger(idx, e)}}>
{renderIngredientName()}
</select>
<input type="number" name="quantity" id="quantity" className="recipe__ingredient__quantity" disabled={disabled} defaultValue={ingredient.quantity} />
{/* <span className={"float"}><Button color="danger" onClick={() => splice(idx)} className={'edit delete-ingredient'}>x</Button></span> */}
</li>
);
}
// eslint-disable-next-line
const edit = () => {
let edit = [{name:"", quantity: null}];
return edit.map((ingredient, idx) =>
<li key={`${ingredient}-${idx}-${recipe._id}`}>
<select name="name" className="edit recipe__ingredient__name" value={ingredient.name || ""} onChange={(e) => {handleChanger(idx, e)}}>
<option value=""></option>
{renderIngredientName()}
</select>
<input type="number" name="quantity" className="edit recipe__ingredient__quantity" value={ingredient.quantity || ""} placeholder="grams" onChange={e => handleChanger(idx, e)} />
<span className={'float'}>
{ idx === edit.length-1 ?
(<Button color="primary" className={'edit create-ingredient'} onClick={(e) => handleAdd(e, idx)}>+</Button>)
:
(<Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'edit delete-ingredient'}>x</Button>)
}
</span>
</li>
)
}
return (
<li key={recipe._id} className="list__item recipe" id={randomString()}>
<Card body id={`card-${recipe._id}`} >
<CardTitle name={randomString()}><input type="text" name="name" defaultValue={recipe.name} readOnly={disabled} className="recipe__name" onChange={(e) => {changeItem(e, recipe)}} /></CardTitle>
<CardImg top width="100%" src={require('../assets/318x180.svg')} alt="Card image cap" />
<input id={String(recipe._id)} className="toggle" type="checkbox" onChange={(e) => {handleChange(e)}} />
<label htmlFor={String(recipe._id)} className="lbl-toggle bold">Ingredients</label>
{/* <span className="bold">Ingredients:</span> */}
<ul key={recipe._id} className="ingredients expand-content" id='ingredientList'>
{iselect()}
{/* {editFields.map((ingredient, idx) => {
return (
<li key={`${ingredient}-${idx}-${recipe._id}`}>
<select name="name" className="edit recipe__ingredient__name" value={ingredient.name || ""} onChange={(e) => {handleChanger(idx, e)}}>
<option value=""></option>
{renderIngredientName()}
</select>
<input type="number" name="quantity" className="edit recipe__ingredient__quantity" value={ingredient.quantity || ""} placeholder="grams" onChange={e => handleChanger(idx, e)} />
<span className={'float'}>
{ idx === editFields.length-1 ?
(<Button color="primary" className={'edit create-ingredient'} onClick={(e) => handleAdd(e, idx)}>+</Button>)
:
(<Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'edit delete-ingredient'}>x</Button>)
}
</span>
</li>
);
})} */}
</ul>
<table>
<tbody>
<tr>
<td className={'center'}><span className={'bold'}>Actual Cost:</span></td>
<td className={'center'}><span className={'bold'}>Proposed Price:</span></td>
</tr>
<tr>
<td className={'center'}><span className="recipe__cost">£{cost(Math.floor(Math.random()*10))}</span></td>
<td className={'center'}><span className="recipe__proposed">£{proposed(cost(Math.floor(Math.random()*10)))}</span></td>
</tr>
</tbody>
</table>
<Button color="primary" onClick={() => { saveEditRecipe(recipe); }} className={`bold save-button ${disabled ? "hidden" : "show"}`}>Save</Button>
<Button color="primary" onClick={(e) => { editRecipe(e, recipe); }} id={`card-${recipe._id}`} className={'edit-button'}>Edit</Button>
<Button color="danger" onClick={() => { deleteRecipe(recipe); }} className={'delete'}>X</Button>
</Card>
</li>
);
};
return (
<div className="recipe">
<ul className="list">
{(recipes && recipes.length > 0) ? (
recipes.map(recipe => renderRecipe(recipe))
) : (
<p>No recipes found</p>
)}
<li key='new' className="add__item list__item recipe">
<Card body>
<CardTitle><input type="text" name="name" id="name" placeholder="New Recipe" className="recipe__name" onChange={(e) => {handleChange(e)}} /></CardTitle>
<CardImg top width="100%" src={require('../assets/318x180.svg')} alt="Card image cap" />
<span className={'bold'}>ingredients
{/* <span className={'float'}><Button color="primary" className={'create-ingredient'} onClick={(e) => handleAdd(e)}>+</Button></span> */}
</span>
<ul className="ingredients" id='ingredientList'>
{fields.map((field, idx) => {
return (
<li key={`${field}-${idx}`}>
<select name="name" className="recipe__ingredient__name" value={field.name || ""} onChange={(e) => {handleChanger(idx, e)}}>
<option value=""></option>
{renderIngredientName()}
</select>
<input type="number" name="quantity" className="recipe__ingredient__quantity" value={field.quantity || ""} placeholder="grams" onChange={e => handleChanger(idx, e)} />
{/* <span className={'float'}><Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'delete-ingredient'}>x</Button></span> */}
<span className={'float'}>
{idx === fields.length - 1 ?
(<Button color = "primary" className = { 'create-ingredient' } onClick = { (e) => handleAdd(e) }>+</Button>)
:
(<Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'delete-ingredient'}>x</Button>)
}
</span>
</li>
);
})}
</ul>
<CardText><span className={'bold'}>Calculated Cost:</span></CardText>
<CardText><span className={'bold'}>Calculated Price:</span></CardText>
<Button color="success" className={'new-recipe, bold'} onClick={() => { createRecipe(newRecipe); }}>Create new recipe</Button>
</Card>
</li>
</ul>
</div>
);
}
export default Recipe;
You didn't share your full code, but it should look something like this:
state = {
editableId:null
}
editRecipe = (event,recipe) => {
this.setState(({editableId:recipe._id}))
}
<Button disabled={!this.state.editableId == recipe._id} color="primary" onClick={(e) => { editRecipe(e, recipe); }} id={`card-${recipe._id}`} className={'edit-button'}>Edit</Button>
When you want no items to be editable, reset the state.editableId to null
Related
I can add select box in loop with one of input box and i want to get every value of select option with input value. right now i am get only last value of select option and also last value of input box. but can get each and every value of select option and input ans. any one help me what is wrong in this code. thanks
here is my code...
export default function AccountRecoveryModal(props) {
const divStyle = {
display: props.displayModal ? 'block' : 'none'
};
function closeModal(e) {
e.stopPropagation()
props.closeModal = false;
}
const [select, setSelected] = useState('');
const [question, setQuestion] = useState([]);
const [ans, setAns] = useState([]);
const [optionList,setOptionList] = useState([]);
const [userinfo, setUserInfo] = useState({
question: [],
answer: [],
});
const fetchData = () => {
axios
.get(Recovery_Questions_URL)
.then((response) => {
const { data } = response;
if(response.status === 200){
//check the api call is success by stats code 200,201 ...etc
setOptionList(data.data)
}else{
//error handle section
}
})
.catch((error) => console.log(error));
};
const [sampleArray, setSampleArray] = useState([])
let temp = [];
const setQueAns = (e, type) => {
console.log(e)
temp.push({key:type,value:e});
const ary = [...temp,{key:type,value:e}]
setQuestion({key:type,value:e})
setQuestion( [...temp, ary])
const listItems = ary.map((temps) =>
<>{temps.value}</>
);
console.log(question);
}
const keys = question.key;
const setData = () => {
axios
.post(Recovery_Ans_URL, {
user_id: props.userId,
[keys] : question.value
})
.then((response) => {
const {data} = response;
console.log(response)
if (response.status === 200) {
if (response.data != null) {
alert('done');
} else {
alert(response.data.message);
}
} else {
//error handle section
}
})
.catch((error) => console.log(error));
};
useEffect(()=>{
fetchData();
},[])
const initialValues = {
optionList
};
return (
<>
<div className="modal" id="accountRecovery" style={divStyle}>
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header row mx-0 border-0 shadow-none">
<div className="col-md-12 d-flex">
{/*<img src="assets/images/icon/layer1.svg" alt="" className="pe-2"*/}
{/* style="width: 30px;" /> */}
<h4 className="modal-title text-white">Account Recovery</h4>
</div>
</div>
<div className="modal-body row mx-0">
<form className="py-0">
{optionList.map((item) => (
<>
<select className="form-select fill border-0 shadow-none font-13 py-2 my-3"
aria-label="Default select example" onChange={(e) => setQueAns(e.currentTarget.value, "recovery_question_"+`${item.question_id}`)}>
<option selected>Select Question</option>
<option key={item.question_id} name={"recovery_question_"+`${item.question_id}`} value={item.question_id} >
{item.question}
</option>
</select>
<div className="col mt-3 form-float btn-add">
<input type="text"
className="form-control fill border-0 shadow-none font-13 py-2 my-3"
placeholder="Your Answer" name={"recovery_answer_"+`${item.question_id}`} onChange={(e) => setQueAns(e.currentTarget.value, "recovery_answer_"+`${item.question_id}`)} />
<label className="floating-label font-13 text-white">Your Answer</label>
</div>
</>
)
)}
</form>
</div>
<div className="modal-footer border-0 mx-0">
<div className="row w-100">
<div className="col-md-6 my-1">
<a className="login-btn btn-hover color-1" href="#" data-bs-target="#important"
data-bs-toggle="modal" onClick={setData}>Next</a>
</div>
</div>
</div>
</div>
</div>
<button type="button" className="btn-close rounded-circle opacity-100"
data-bs-dismiss="modal"></button>
</div>
</>
);
}
```[enter image description here](https://i.stack.imgur.com/JIlEw.png)
I'm trying to build a CURD operation component that GET the data from API. When a user clicks on the EDIT, it renders the component showing the details of the line item to be edited. But when the user hits the back button in the browser, the previous component with the post list re-renders and it loses the previous state and scroll position. Is there a way that I can bring the selected row to the first position if I scroll up it displays the previous data and if I scroll down it shows the next coming data?
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import { CSVLink, CSVDownload } from "react-csv";
import "./Home.css";
const Home = () => {
const [loading, setLoading] = useState(false);
const [users, setUsers] = useState([]);
const [searchTitle, setSearchTitle] = useState("");
const [prime, setPrime] = useState([]);
const [primeid, setPrimeid] = useState("");
const [category, setCategory] = useState([]);
const [categoryid, setCategoryid] = useState("");
const [subcategory, setSubcategory] = useState([]);
const [subcategoryid, setSubcategoryid] = useState("");
const usersList = users.length;
useEffect(() => {
loadUsers();
}, []);
// the below command is used to send the load data from the database
const loadUsers = async () => {
const result = await axios.get(`http://localhost:4000/api/email/`);
// setUsers(result.data);
setUsers(result.data);
// console.log(result)
};
useEffect(() => {
const getprime = async () => {
const resprime = await axios.get(
`http://localhost:4000/api/primewise${primeid}`
);
setPrime(resprime.data.reverse());
};
getprime();
}, []);
const handleprime = (event) => {
const getprimeid = event.target.value;
setPrimeid(getprimeid);
};
useEffect(() => {
const getcategory = async () => {
const rescategory = await axios.get(
"http://localhost:4000/api/categorywise"
);
setCategory(rescategory.data.reverse());
};
getcategory();
}, []);
const handlecategory = (event) => {
const getcategoryid = event.target.value;
setCategoryid(getcategoryid);
};
useEffect(() => {
const getsubcategory = async () => {
const ressubcategory = await axios.get(
"http://localhost:4000/api/subcategorywise"
);
setSubcategory(ressubcategory.data.reverse());
};
getsubcategory();
}, []);
const handleSubcategory = (event) => {
const getSubcategoryid = event.target.value;
setSubcategoryid(getSubcategoryid);
};
useEffect(() => {
if (users.length) {
const scrollPosition = sessionStorage.getItem("scrollPosition");
if (scrollPosition) {
window.scrollTo(0, parseInt(scrollPosition, 10));
sessionStorage.removeItem("scrollPosition");
}
}
}, [users]);
useEffect(() => {
if (users.length) {
const scrollPosition = sessionStorage.getItem("scrollPosition");
if (scrollPosition) {
window.scrollTo(0, parseInt(scrollPosition, 10));
sessionStorage.removeItem("scrollPosition");
}
}
}, [users]);
return (
<div className="App">
<div className="header-1">
<nav className="navbar">
<div className="container-fluid">
<div className="navbar-brand">Supplier Contact Information</div>
{/* <form className="d-flex">
<Link className="btn btn-outline-light" to="/users/statics">
Statics
</Link>
</form> */}
</div>
</nav>
<hr class="solid"></hr>
</div>
<div className="header">
<h4>CATEGORYWISE SUPPLIER INFORMATION</h4>
<div className="col-md-0">
<div class="row">
<div class="col-sm-3">
<div class="card">
<div class="card-body text-center ">
<input
type="text"
placeholder="Power Search..."
className="form-control"
onChange={(e) => setSearchTitle(e.target.value)}
/>
<br />
<select
name="prime"
className="form-control"
onChange={(e) => handleprime(e)}
>
<option value="">--Select Prime--</option>
{prime.map((resprime, index) => (
<option key={index} value={resprime.Prime_Descr}>
{resprime.Prime_Descr}{" "}
</option>
))}
</select>{" "}
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<select
name="category"
className="form-control"
onChange={(e) => handlecategory(e)}
>
<option value="">--Select Category--</option>
{category.map((rescategory, index) => (
<option key={index} value={rescategory.CATEGORY}>
{rescategory.CATEGORY}{" "}
</option>
))}
</select>{" "}
<br />
<select
name="country"
className="form-control"
onChange={(e) => handlecategory(e)}
>
<option value="">--Select Category--</option>
{subcategory.map((ressubcategory, index) => (
<option key={index} value={ressubcategory.SUB_CATEGORY}>
{ressubcategory.SUB_CATEGORY}{" "}
</option>
))}
</select>{" "}
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card-1">
<div class="card-body text-center ">
<CSVLink
filename="supplier contact info."
data={users}
class="btn btn-outline-light"
>
Export to csv
</CSVLink>
<br />
<br />
<div className="container-fluid">
<Link className="btn btn-outline-light" to="/users/add">
Add Supplier
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="outer-wrapper">
<div className="tabel-wrapper">
<table>
<thead>
<th scope="col">S.No</th>
<th scope="col">Prime</th>
<th scope="col">Category</th>
<th scope="col">Sub-Category</th>
<th scope="col">Supplier</th>
<th scope="col">Country</th>
<th scope="col">Region</th>
<th scope="col">Title/Designation</th>
<th scope="col">Representative</th>
<th scope="col">Department</th>
<th scope="col">Primary Contact No</th>
<th scope="col">Secondary Contact No</th>
<th scope="col">EmailId</th>
<th scope="col">Address</th>
<th>Action</th>
</thead>
<tbody>
{/* {usersList ? (
<h4>Loading ...</h4> */}
{loading ? (
<h4>Loading ...</h4>
) : (
users
.filter((value) => {
if (searchTitle === "") {
return value;
} else if (
// (value.PRIME &&
// value.PRIME.toLowerCase().includes(
// searchTitle.toLowerCase()
// )) ||
(value.CATEGORY &&
value.CATEGORY.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.SUB_CATEGORY &&
value.SUB_CATEGORY.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.SUPPLIER &&
value.SUPPLIER.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.COUNTRY &&
value.COUNTRY.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.REGION &&
value.REGION.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.DESIGNATION &&
value.DESIGNATION.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.REPRESENTATIVE_NAME &&
value.REPRESENTATIVE_NAME.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.DIVISION &&
value.DIVISION.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.CONTACT_NO &&
value.CONTACT_NO.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.ALTERNATE_CONTACT_NUMBER &&
value.ALTERNATE_CONTACT_NUMBER.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.EMAIL_ID &&
value.EMAIL_ID.toLowerCase().includes(
searchTitle.toLowerCase()
)) ||
(value.ADDRESS &&
value.ADDRESS.toLowerCase().includes(
searchTitle.toLowerCase()
))
) {
return value;
}
})
.map((user, index) => (
<tr>
<td scope="row">{index + 1}</td>
<td>{user.PRIME}</td>
<td>{user.CATEGORY}</td>
<td>{user.SUB_CATEGORY}</td>
<td>{user.SUPPLIER}</td>
<td>{user.COUNTRY}</td>
<td>{user.REGION}</td>
<td>{user.DESIGNATION}</td>
<td>{user.REPRESENTATIVE_NAME}</td>
<td>{user.DIVISION}</td>
<td>{user.CONTACT_NO}</td>
<td>{user.ALTERNATE_CONTACT_NUMBER}</td>
<td>{user.EMAIL_ID}</td>
<td>{user.ADDRESS}</td>
<td>
<Link
class="btn btn-outline-primary mr-2"
to={`/users/edit/${user.ID}`}
>
Edit
</Link>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default Home;
I have a table full of text inputs. All the inputs are stored in a React state array (newUserPledges). If I have values in all the text inputs and add a new row, the new row is duplicating the previous row input values. I can't see why it would be doing that.
Here is my component with all the irrelevant code dotted out.
import React, { useEffect, useState } from 'react';
...
const initialNewPledgeState = {
campaign: '',
date: dateFormatted(new Date()),
pledges: 0,
howPaid: '',
confirmation: '',
sponsor: '',
notes: ''
};
const buttonRef = React.createRef();
const AdminControlPanel = () => {
...
const [addPledgeOptions, setAddPledgeOptions] = useState([]);
const [selectedDeleteUser, setSelectedDeleteUser] = useState('');
const [selectedEditUser, setSelectedEditUser] = useState('');
const [selectedPledgeUser, setSelectedPledgeUser] = useState('');
const [selectedAddPledgeUser, setSelectedAddPledgeUser] =
useState('');
const [allUsers, setAllUsers] = useState([]);
const [usersWithPledges, setUsersWithPledges] = useState([]);
const [usersWithPledgesCount, setUsersWithPledgesCount] =
useState(0);
const [userPledges, setUserPledges] = useState([]);
const [newUserPledges, setNewUserPledges] = useState([
initialNewPledgeState
]);
const alert = useAlert();
const setUpUsersArrays = () => {
getUsers('user').then((r) => {
setAllUsers(r.data.users);
const firstAddPledgeOption = [
{ value: '', label: 'Select a user to add pledges for...' }
];
const usersOptions = r.data?.users?.map((u) => {
return {
value: u.email,
label: `${u.email} - ${u.last_name}, ${u.first_name}`
};
});
if (usersOptions?.length) {
setAddPledgeOptions(
firstAddPledgeOption.concat(usersOptions)
);
} else {
setOptions([]);
setEditOptions([]);
}
});
getUsersWithPledges().then((r) => {
const firstPledgeOption = [
{ value: '', label: 'Select a user to view pledges for...' }
];
const usersOptions = r.data?.users?.map((u) => {
return {
value: u.email,
label: `${u.email} - ${u.last_name}, ${u.first_name}`
};
});
if (usersOptions) {
setUsersWithPledgesCount(usersOptions.length);
setUsersWithPledges(firstPledgeOption.concat(usersOptions));
}
});
};
useEffect(() => {
getDatabaseStats().then((r) => {
setDbStats(r.data.stats);
});
setUpUsersArrays();
}, []);
useEffect(() => {
console.log(selectedDeleteUser.value);
}, [selectedDeleteUser]);
...
const handlePledgeChange = (e, i) => {
// console.log(e.target);
const value = e.target.value;
const pledge = newUserPledges[i];
console.log({ pledge });
pledge[e.target.name] = value;
const tempPledges = [...newUserPledges];
tempPledges[i][e.target.name] = value;
// tempPledges.splice(i, 1, pledge);
setNewUserPledges(tempPledges);
// const value = e.target.value;
// setInputState({
// ...inputState,
// [e.target.name]: value
// });
};
const addNewPledgeRow = (e) => {
e.preventDefault();
const tempPledges = [...newUserPledges].concat(
initialNewPledgeState
);
console.log({ tempPledges });
setNewUserPledges(tempPledges);
};
const deletePledgeRow = (e) => {
e.preventDefault();
const tempPledges = [...newUserPledges];
tempPledges.pop();
setNewUserPledges(tempPledges);
};
const customStyles = {
menuList: (base) => ({
...base,
// kill the white space on first and last option
padding: 0,
minHeight: '300px'
})
};
return (
<div className={'container admin'}>
<h2>Admin Control Panel</h2>
...
<div
className={
'col-12 col-md-9 mx-auto my-4 p-1 border border-secondary bg-light accordion'
}
id={'admin-accordion-add-pledges'}
>
<div className={'accordion-item'}>
<h2 className={'accordion-header'} id={'headingAddPledges'}>
<button
className={'accordion-button collapsed'}
type={'button'}
data-bs-toggle={'collapse'}
data-bs-target={'#collapseAddPledges'}
aria-expanded={'true'}
aria-controls={'collapseAddPledges'}
>
<h4 className={'text-center'}>Add Pledges For User</h4>
</button>
</h2>
<div
id={'collapseAddPledges'}
className={'accordion-collapse collapse mt-1 px-2'}
aria-labelledby={'headingAddPledges'}
data-bs-parent={'#admin-accordion-add-pledges'}
>
{!!addPledgeOptions.length ? (
<Select
className={'mb-2'}
value={selectedAddPledgeUser}
onChange={(e) => setSelectedAddPledgeUser(e)}
options={addPledgeOptions}
styles={customStyles}
/>
) : (
<h4 className={'text-center'}>
No users to add pledges for
</h4>
)}
{selectedAddPledgeUser?.value?.length > 0 && (
<form>
<div className={'my-2 user-pledges'}>
<table className={'table table-bordered'}>
<thead>
<tr>
<th width={100}>Campaign</th>
<th width={120}>Date</th>
<th width={80}>Pledges</th>
<th>How Paid</th>
<th>Confirmation</th>
<th>Sponsor</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{!!newUserPledges.length &&
newUserPledges.map((p, i) => (
<tr key={i}>
<td>
<input
type={'text'}
className={'form-control'}
value={p.campaign}
name={'campaign'}
id={`campaign-${i}`}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
<td>
<input
type={'date'}
max={dateFormatted(new Date())}
className={'form-control'}
value={p.date}
name={'date'}
id={`date-${i}`}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
<td>
<input
type={'text'}
className={'form-control'}
value={p.pledges}
name={'pledges'}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
<td>
<input
type={'text'}
className={'form-control'}
value={p.howPaid}
name={'howPaid'}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
<td>
<input
type={'text'}
className={'form-control'}
value={p.confirmation}
name={'confirmation'}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
<td>
<input
type={'text'}
className={'form-control'}
value={p.sponsor}
name={'sponsor'}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
<td>
<input
type={'text'}
className={'form-control'}
value={p.notes}
name={'notes'}
onChange={(e) =>
handlePledgeChange(e, i)
}
/>
</td>
</tr>
))}
</tbody>
</table>
<div className={'d-flex justify-content-evenly'}>
<button
className={'btn btn-primary'}
onClick={addNewPledgeRow}
disabled={newUserPledges.length > 9}
>
<FontAwesomeIcon icon={faPlusSquare} />
Add New Row
</button>
<button
className={'btn btn-primary'}
onClick={deletePledgeRow}
disabled={newUserPledges.length === 1}
>
<FontAwesomeIcon icon={faMinusSquare} />
Delete Row
</button>
<button className={'btn btn-success'}>
Submit Pledges
</button>
<button className={'btn btn-danger'}>
Reset Table
</button>
</div>
</div>
</form>
)}
</div>
</div>
</div>
<br />
</div>
);
};
export default AdminControlPanel;
Here is an example of what is happening. I fill in values in the first row, and then hit add row and it adds a second row with duplicated values.
Asked my question in the Reactiflux Discord and got an answer right away. I was mutating my rows state in the handlePledgeChange function.
I have changed that function to this and it works great now.
const handlePledgeChange = (e, i) => {
const value = e.target.value;
const tempPledges = newUserPledges.map((item, idx) => {
if (i === idx) return { ...item, [e.target.name]: value };
return item;
});
setNewUserPledges(tempPledges);
};
I have an input and a select starting when placing any value in the case of the input, it begins to generate dynamic statements and in the case of the input, dynamic inputs are generated but in decrement, everything works fine, I only need that the dynamic inputs that begin to appear are show one by one and not all piled up as I show in the picture.
problem image
how do i want it to be
import React, { useState } from "react";
//input dynamic
import Row from "./Row2";
let initialState = {
first: null,
arraySelect: []
};
function Pruebas(props) {
/*input dynamic */
const [rows, setRows] = useState([]);
const [initialeRow, setInitialRow] = useState({ nombre: "" });
const handleOnChange = (index, value) => {
const copy = rows.map((e, i) => {
if (i === index) {
e.nombre = value;
}
return e;
});
setRows([...copy]);
};
const handleOnAdd = () => {
if (initialeRow.nombre >= 1) {
setInitialRow({ nombre: initialeRow.nombre - 1 });
setRows([...rows, initialeRow]);
}
};
/////////////////////////////////////////////////////
const [input_multi, setInput_multi] = useState();
const [arraySelect, setarraySelect] = useState(initialState.arraySelect);
const [numberIni, setnumberIni] = useState(initialState.first);
const getArray = (value) => {
let arr = [];
{
let reco = Math.round(numberIni - parseInt(value));
console.log(reco);
if (parseInt(value) == numberIni) {
return false;
}
Array(reco)
.fill(1)
.map((value2, key) => {
arr.push(parseInt(value) + parseInt(key + 1));
});
}
return arr;
};
const setSelect = (value) => {
let isArray = getArray(value);
if (isArray) {
setarraySelect([...arraySelect, isArray]);
}
//input dynamyc
if (initialeRow.nombre >= 1) {
setInitialRow({ nombre: initialeRow.nombre - 1 });
setRows([...rows, initialeRow]);
}
};
//input dynamic
const handleInput_division = (event) => {
setInitialRow({ nombre: event.target.value });
};
const handleSubmit = (event) => {
event.preventDefault();
setnumberIni(event.target.numberIni.value);
};
const resetForm = () => {
setnumberIni(null);
setarraySelect([]);
};
return (
<div>
<form onSubmit={handleSubmit}>
<div class="row">
<label>PHASES</label>
<div class="col-sm-6">
<h6>enter a number</h6>
<div class="input-group ">
<input
type="number"
name="numberIni"
placeholder="0"
class="form-control"
value={input_multi}
onChange={(event) => setInput_multi(event.target.value)}
/>
<br />
<button type="submit" className="btn btn-success"> <i class="far fa-save"></i></button>
<br />
<div class="col-sm-6">
<h6>2 - # input dynamic</h6>
<div class="input-group ">
<select name='numberIni2' class='form-control' onChange={handleInput_division}>
<option value='no' selected>
Seleccione </option>
<option value='2'> 2</option>
<option value='3'>3
</option>
</select>
<br/>
</div>
</div>
</div>
</div>
</div>
</form>
<br />
<div class="col-sm-12 btn btn-primary">
<b>PHASES</b>
</div>
<br /> <br />
<div class="row">
<div class="col-sm-12">
{numberIni && (
<div class="col-sm-12">
<label>
<font size="2">
1° PHASES <br />
select a number : {" "}
</font>
</label>
<select onChange={(e) => setSelect(e.target.value)} name="" id="">
<option value="seleccione">Seleccione</option>
{Array(parseInt(numberIni))
.fill(1)
.map((value, key) => {
return <option value={key + 1}>{key + 1} equipment</option>;
})}
</select>
<label>
<font size="2"> equipment </font>{" "}
</label>
{Array(parseInt(numberIni))
.fill(1)
.map((value, key2) => {
return (
<div>
{arraySelect[key2] && (
<>
<label>
<font size="2">
2° select another number <br />
classify: {" "}
</font>{" "}
</label>
<select
onChange={(e) => setSelect(e.target.value)}
name=""
id=""
>
<option value="seleccione">Seleccione</option>
{arraySelect[key2].map((value, key3) => {
return (
<option value={arraySelect[key2][key3]}>
{arraySelect[key2][key3]} equipment
</option>
);
})}
</select>
{rows.map((e, index) => (
<Row
nombre={e.nombre}
index={index}
onChange={(index, value) => handleOnChange(index, value)}
key={index}
/>
))}
</>
)}
</div>
);
})}
</div>
)}
</div>
</div>
{numberIni && (
<input
onClick={() => resetForm()}
type="button"
className="btn btn-danger"
value="restart phase"
/>
)}
</div>
);
}
export default Pruebas;
//Row2
const Row = (props) => {
const { onChange, onRemove, nombre, index } = props;
console.log(props);
return (
<div>
<input
disabled
value={nombre}
onChange={(e) => onChange(index, e.target.value)}
placeholder="Decrementar"
/>
</div>
);
};
export default Row;
1st column is non-editable and 2nd column is editable, when user searches, any name and try to edit 2nd column, even 2 rows are editable, index is not passing appropriately, how can i fix the issue
Below is the link which am refering
https://codesandbox.io/s/customtablereact-forked-drnc0
what am missing here
please refer below snippet
import React from "react";
export const CustTable = ({ columns, data, setData, search, setSearch }) => {
const [editable, setEditable] = React.useState("");
const cellEditable = (label) => {
data.map((l) => {
if (l.name === label.name) {
setEditable(label.name);
}
});
};
const changeLastName = (name, e, index) => {
e.persist();
setData((prevData) => {
return [
...prevData.slice(0, index),
{ name, lname: e.target.value },
...prevData.slice(index + 1)
];
});
};
const items = () => {
let resultItems = data;
if (search) {
resultItems = data.filter((item) =>
item.name.toLowerCase().includes(search.toLowerCase())
);
}
return resultItems.map((label, index) => {
return (
<tr style={{ display: "flex" }} key={index}>
<span style={{ width: "35%" }}>{label.name} </span>
{editable === label.name ? (
<input
type="text"
value={label.lname}
onChange={(e) => changeLastName(label.name, e, index)}
/>
) : (
<span onClick={() => cellEditable(label)}>{label.lname} </span>
)}
</tr>
);
});
};
const searchData = (e) => {
setSearch(e.target.value);
};
return (
<>
<div>
<input
type="search"
placeholder="search"
onChange={(e) => searchData(e)}
/>
</div>
{items()}
</>
);
};
you have to check "AlreadyEditable" varible like given blow
let AlreadyEditable = 0;
return resultItems.map((label, index) => {
if (editable === label.name) {
AlreadyEditable += 1;
}
return (
<tr style={{ display: "flex" }} key={index}>
<span style={{ width: "35%" }}>{label.name} </span>
{AlreadyEditable < 2 && editable === label.name ? (
<input
type="text"
value={label.lname}
onChange={(e) => changeLastName(label.name, e, index)}
/>
) : (
<span onClick={() => cellEditable(label)}>{label.lname} </span>
)}
</tr>
);
});