input text field for a particular item in array while mapping? - javascript

Im trying to map over an array and trying to update a particular item based on a button click.
<div className="read-data">
{readData.length >= 1 ? readData.map((res)=>(
<div>
<h2>{res.data.message}</h2>
<button onClick={()=>{
(setUpdateBox(true))
}}>
Update
</button>
{updateBox ? (
<div>
<form>
I<input type="text" name="textarea" placeholder="Update message"/>
</form>
</div>
):null}
<button onClick={()=>{
deleteMessage(res)}}> Delete</button>
</div>
)):null}
</div>
it opens text area for every item in array. but i want input text box for the only item whos corresponding button is clicked.
plz help how to do it.

you may find that solution useful for you.
you need to set a unique index or any identifier to every element.
on every update or delete operation, you can access the element by its index
reference the input to get the value by using ref
I assume that you pass an array of object as a props
const readData = [
{
data: {
message: "new message 1"
}
},
{
data: {
message: "new message 2"
}
},
{
data: {
message: "new message 3"
}
},
{
data: {
message: "new message 4"
}
}
];
ReadData Component
function ReadDataComponent({readData}) {
let inputRef = React.useRef();
const [updateBox, setUpdateBox] = React.useState(readData);
const deleteMessage = (i) => {
const updatedBox = [...updateBox];
let filteredBox = updatedBox.filter((_, index) => i !== index);
setUpdateBox(filteredBox);
};
const showUpdateBox = (i) => {
const updatedBox = [...updateBox];
updatedBox[i].isOpen = updatedBox[i].isOpen ? !updatedBox[i].isOpen : true;
updatedBox[i].data.message = inputRef.current
? inputRef.current.value
: updatedBox[i].data.message;
setUpdateBox(updatedBox);
};
return (
<div className="read-data">
{readData.length >= 1
? updateBox.map((res, i) => (
<div key={i}>
<h2>{res.data.message}</h2>
<button
onClick={() => {
showUpdateBox(i);
}}
>
Update
</button>
{res.isOpen ? (
<div>
<form>
<input
ref={inputRef}
type="text"
name="textarea"
placeholder="Update message"
/>
</form>
</div>
) : null}
<button
onClick={() => {
deleteMessage(i);
}}
>
{" "}
Delete
</button>
</div>
))
: null}
</div>
);
}
live demo

You could do something like this. Not sure if you want to use state or props or both etc.
Have a updateBox property on every object in the array, and toggle it between true and false.
You should probably add another function to update the message property as the user types in the input box?
function App(){
function setUpdateBox(id){
const copyState = [...readData];
const thisItem = copyState.find(x => x.id == id);
thisItem.updateBox = !thisItem.updateBox;
setData(copyState);
}
function deleteMessage(id){
const copyState = [...readData].filter(x => x.id != id);
setData(copyState);
}
const initData = [
{id: 1, updateBox: true, data: {message: "hello"}},
{id: 2, updateBox: true, data: {message: "world"}},
]
const [readData, setData] = React.useState(initData);
return (
<div className="read-data">
{readData.length >= 1 ? readData.map((res)=>(
<div>
<h2>{res.data.message}</h2>
<button onClick={()=>setUpdateBox(res.id)}>
Update
</button>
{res.updateBox ? (
<div>
<form>
<input type="text" name="textarea" placeholder="Update message"/>
</form>
</div>
):null}
<button onClick={()=>deleteMessage(res.id)}> Delete</button>
</div>
)):null}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

onClick function is not called after I have enabled the button in Reactjs

I have a textarea and a button. The button is disabled by default and when the user starts typing, I enable the button to be clicked. But the problem is that, the onClick function is not called while already disabled = false was set.
I've seen this: button onClick doesn't work when disabled=True is initialized (Reactjs)
Seems to be a good idea, but after I setState with the new value, my component is re-rendering, and I don't really want that.
const refText = useRef(null);
const refBtn = useRef(null);
function handleBtnStatus(e) {
let text = e.target.value;
if(text.replace(/\s/g, "").length > 0) {
refBtn.current.disabled = false;
}
else {
refBtn.current.disabled = true;
}
}
function postThis() {
console.log("You posted! Text:", refText.current.value);
// disable again
refBtn.current.disabled = true;
// delete previous text wrote
refText.current.value = "";
}
return (
<>
{isLogged && (
<div className="container">
<div className="content">
<div className="utool-item-text">
<textarea name="textArea" placeholder="Write something.." ref={refText} onChange={(e) => handleBtnStatus(e)}></textarea>
</div>
<div className="utool-item-post">
<button className="ust-btn-post" ref={refBtn} disabled={true} onClick={postThis}>Da Tweet</button>
</div>
</div>
<div className="posts-section">
<div className="list-posts">
{posts.map((p) => {
return (p.hidden === false ? (
<div className="post" key={p.id}>
<div className="post-text">
<span>{p.text}</span>
</div>
</div>
) : (''))
})}
</div>
</div>
</div>
)}
</>
)
Any help?
Use state instead of refs, re-rendering is ok for your case
Simplified example:
import React, { useState } from 'react';
const SimpleExample = () => {
const [textAreaValue, setTextAreaValue] = useState('');
return (
<>
<button disabled={!textAreaValue} onClick={() => console.log('onClick handler')}>
click me
</button>
<textarea value={textAreaValue} onChange={(e) => setTextAreaValue(e.target.value)} />
</>
);
};
And I would recommend checking this Use state or refs in React.js form components?

How to handle mapped elements individually by a button of each created while mapping array?

I have created a carousel which is a column of ten dates ,for this i am mapping dates by momentjs, Inside each of this column , i am mapping different time slots for morning ,afternoon and evening,
and i have a functionality that only shows first two time slots and then there is a show more button, by clicking on this button more time slots are appear,but whenver i am clicking on this button all of the columns time slots is appearing, i have to handle all the column button individually..
Thank You in adavance... :)
below is my code...
const [showMoreClicked, setShowMoreClicked] = useState(false);
const [showMoreAfternoon, setShowMoreAfternoon] = useState(false);
const [showMoreEvening, setShowMoreEvening] = useState(false);
const showMoreSlotsForMorning = (e) => {
e.preventDefault();
setMoreClicked(!showMoreClicked);
};
const showMoreSlotsForAfternoon = (e) => {
e.preventDefault();
setShowMoreAfternoon(!showMoreAfternoon);
};
const showMoreSlotsForEvening = (e) => {
e.preventDefault();
setShowMoreEvening(!showMoreEvening);
};
<Carousel responsive={responsive}>
{nexttendates.map((elem, dateIndex) => {
return (
<div>
<button key={dateIndex} className="nexttendates">
{elem}
</button>
<div className="appointment-timelots">
<div className="availableslots">
<div className="availableslot">
<img
src="../elements/doctorlist/doctorcard/sunrise.png"
alt=""
className="sunrise"
/>
Morning
</div>
</div>
</div>
{morningtime.map((elem, morInd, arr) => {
if (showMoreClicked == false) {
while (morInd == 0 || morInd == 1)
return (
<button key={morInd} className="appointtimes">
{elem}
</button>
);
} else {
return (
<button key={morInd} className="appointtimes">
{elem}
</button>
);
}
})}
<button
choseIndex={dateIndex}
onClick={showMoreSlotsForMorning}
className="appointtimes"
>
{showMoreClicked ? "Show Less" : "Show More"}
</button>
<img
src="../elements/doctorlist/doctorcard/sun.png"
alt=""
className="afternoon"
/>
Afternoon
{afternoontime.map((elem, aftInd) => {
if (showMoreAfternoon == false) {
while (aftInd == 0 || aftInd == 1)
return (
<button className="appointtimes">{elem}</button>
);
} else {
return (
<button className="appointtimes">{elem}</button>
);
}
})}
<button
choseIndex={dateIndex}
onClick={showMoreSlotsForAfternoon}
className="appointtimes"
>
{showMoreAfternoon ? "Show Less" : "Show More"}
</button>
<img
src="../elements/doctorlist/doctorcard/night-mode.png"
alt=""
className="evening"
/>
Evening
{eveningtime.map((elem, eveInd) => {
if (showMoreEvening == false) {
while (eveInd == 0 || eveInd == 1) {
return (
<button className="appointtimes">{elem}</button>
);
}
} else {
return (
<button className="appointtimes">{elem}</button>
);
}
})}
<button
choseIndex={dateIndex}
onClick={showMoreSlotsForEvening}
className="appointtimes"
>
{showMoreEvening ? "Show Less" : "Show More"}
</button>
</div>
);
})}
</Carousel>
i think its happening because of i have mapped an array and only used one useState to check open or not...Can anybody plz help me....
Make the time slots list as a separate component, so that each of the morning, afternoon, and evening list will have their own state automatically for toggling display.
Something like this example:
import { useState } from "react";
// Toggle showMore value on click
const SlotsList = ({ slots }) => {
const [showMore, setShowMore] = useState(false);
const handleShowMoreClick = () => {
setShowMore((prev) => !prev);
};
// Filter the slots prop before map it if showMore is false
return (
<div>
{slots
.filter((elem, index) => (showMore ? true : index <= 1))
.map((elem, index) => (
<button key={index} className="appointtimes">
{elem}
</button>
))}
<button onClick={handleShowMoreClick} className="appointtimes">
{showMore ? "Show Less" : "Show More"}
</button>
</div>
);
};
export default SlotsList;
In this example, the list is filtered before being mapped out for an easier solution. The key property should be replaced by a unique ID to avoid conflict.
It can then be imported and used like below in the main component. Also reusable for all 3 lists, and each have separate display toggle.
<SlotsList slots={morningtime} />

Why my form input is typing per 1 letter and then stop if passing props

I know this before but I forgot how did I do it..so basically let say I have
const [showItem,setShowItem] = useState({})
const [updateName,setUpdateName] = useState('')
and then I have a props function that will do something like this...this props is callable for array of items and I want to do this to make it more cleaner and reuseable.
<ItemsEdit items={showItem} setUpdateName_= { setUpdateName } updateName = { updateName } ></ItemsEdit>
Now as you see, when I'm trying to pass my setUpdateName_ and updatename.
UPDATE
This is the map for my items that will call the <ItemsEdit/> for specific id only in my buttons. (but this not affect anything in form)
{nfts.map((nft,i) => {
return(
<div
className="items"
key={nft.id}
>
{showItem.id == nft.id ? <>
<form onSubmit={handleSubmit}>
<ItemsEdit
items={showItem}
setUpdateName= { setUpdateName }
updateName = { updateName }
/>
</form>
</> : <>
<Items items={nft}></Items>
</>}
</div>
)
})}
and here is the <ItemsEdit/>
so for every key that I press it will lose the focus in input but when I used autoFocus = "autoFocus" in the input text it will works but the only thing is that it will the same text in other items..so its not the best idea for me.
const ItemsEdit = ({items,setUpdateName,updateName}) => {
return (
<>
<input
id='name'
type="text"
key="text"
// autoFocus="autoFocus"
value = {updateName}
placeholder="NFT name"
onChange={ e => setUpdateName( e.target.value )}
></input>
<p>{items.id}</p>
<img src={items.data.img} alt="" />
<div className="buttons">
<button
type='submit'>
Update
</button>
<button type='submit' className='left'
onClick={
() => {
setShowItem({})}
}>
<ion-icon name="arrow-back-circle-outline"></ion-icon>
</button>
</div>
</>
)
}
I now have an answer but this kinda nasty for me
so for the <ItemsEdit/> I would call it something like this
{ItemsEdit(
{items:showItem,
setUpdateName:setUpdateName,
updateName:updateName}
)}
and just remove the return of and change the { } into ( )just like this
const ItemsEdit = ({items,setUpdateName,updateName}) => (
)

Input value not updating, dynamic input box when user opens popup window react/next js

I have a problem where when a user opens a popup window to change their name, email, password, etc. the input box where they'd add their change won't collect their input and display the change.
I'm using react/next.js and my popup window is from the npm package "reactjs-popup"
Note: I don't think the fact that it's a popup window is relevant, just adding that for context. I believe the problem lies in the dynamic creation of the input boxes or something along the lines of that.
Example: user clicks on the edit email option -> popup window opens with an input box for them to change their email, when the user tries to type in that input box nothing shows, when I console log their change it only logs 1 letter at a time, with previous letters being replaced by the new one typed.
I've included the code below that I'm trying to use to make this work.
Code
// element -> identifier so I know which field the user wants to update, ex: element = "full_name"
// title -> the title of the popup, ex: title = "Change Full Name"
// content -> the current value of the field, ex: content = "John Doe"
const EditSettingPopupWindow = ({ element, user_id, title, content }) => {
const [edit, setEdit] = useState("");
const [inputType, setInputType] = useState(<>Loading...</>);
const [popupTitle, setPopupTitle] = useState("");
const [toChange, setToChange] = useState("");
const fullNameInput = {
input: (
<input
id="new-name"
name="new-name"
type="name"
placeholder="Full Name"
value={edit}
onChange={(e) => setEdit(e.target.value)}
autoComplete="name"
required
/>
),
title: "Change Name",
toChange: `Current name: ${`PLACEHOLDER`}`,
};
const dateOfBirth = {...};
const emailInput = {...};
const phoneInput = {...};
const addressInput = {...};
const usernameInput = {...};
const passwordInput = {...};
const billingPlan = {...}
const updateUserPassword = async (password) => {
...change password code
};
useEffect(() => {
switch (element) {
case "full_name":
setInputType(fullNameInput.input);
setPopupTitle(fullNameInput.title);
setToChange(fullNameInput.toChange);
console.log(fullNameInput.title);
break;
case "userId":
setInputType(usernameInput.input);
setPopupTitle(usernameInput.title);
setToChange(usernameInput.toChange);
console.log(usernameInput.title);
break;
...(rest of switch cases)
}
}, [element]);
const handleSubmit = async (e) => {
e.preventDefault();
if (element === "password") {
await updateUserPassword(edit);
setEdit("");
Router.reload();
return;
};
console.log('handleSubmit-not password');
await updateFirestore(user_id, element, edit);
setEdit("");
Router.reload();
};
return (
<>
<Popup
trigger={
<span className={styles.rowContainer}>
<h3 className={styles.rowInfo}>{title}</h3>
<h3 className={styles.rowInfo}>{content}</h3>
<IoIosAddCircleOutline />
</span>
}
modal
nested
>
{/* #ts-ignore */ }
{close => {
return (
<div className="modal">
<button className="close" onClick={close}>
×
</button>
<div className="header"><h3>{popupTitle}</h3></div>
<div className="content">
<h4>{toChange}</h4>
<form onSubmit={(e) => {
handleSubmit(e);
close();
}}>
{inputType}
<button
type="submit"
id="submit"
value="Submit"
>
Submit
</button>
</form>
</div>
<div className="actions">
<button
className="button"
onClick={() => {
console.log('modal closed ');
close();
}}
>
Close
</button>
</div>
</div>
)
}}
</Popup>
</>
)
};
Edit
So I changed the <form> code in the popup to this and it works, however it's not very elegant so I'd still like to use the old way if someone has a suggestion.
<form id="updateSetting" onSubmit={(e) => {
handleSubmit(e);
close();
}}>
{ element === "full_name" ? fullNameInput.input : null}
{ element === "userId" ? usernameInput.input : null}
{ element === "date_of_birth" ? dateOfBirth.input : null}
{ element === "usersAddress" ? addressInput.input : null}
{ element === "billing_plan" ? billingPlan.input : null}
{ element === "email" ? emailInput.input : null}
{ element === "phone_number" ? phoneInput.input : null}
{ element === "password" ? passwordInput.input : null}
<button id="submit" value="submit" type="submit">Submit</button>
</form>

Rendering a box when submitting the data

I have the following code in my React:
const [property, setProperty] = useState([]);
const [state, setState] = React.useState({ type: "", propertyName: "" });
const handleChange = (e, inputField) => {
setState((prevState) => ({
...prevState,
[inputField]: e.target.value,
}));
};
const handleSubmit = () => {
if (state.type !== "" && state.propertyName !== "") {
const newObject = { type: state.type, propertyName: state.propertyName };
property.push(newObject);
console.log(property);
setState({
type: "",
propertyName: "",
});
}
};
And html:
<div>
<label htmlFor='properties' className='properties-label'>
Properties
</label>
<div className='property-box'>
<input
type='text'
id='type'
value={state.type}
placeholder='Type'
className='type-element'
required
onChange={(e) => handleChange(e, "type")}
></input>
<input
type='text'
id='name'
value={state.propertyName}
className='type-element'
placeholder='Name'
required
onChange={(e) => handleChange(e, "propertyName")}
></input>
<button
className='buttonAccount'
type='submit'
onClick={handleSubmit}
>
Add Property
</button>
</div>
</div>
What I want is when I press the add Property button a new html tag will render on the page(a box or something like that containing the two fields that has been inputted). Can you help me find a way to do that?
You have to print the elements in your property array. For exmaple:
{
property.map((element) => (
<div key={element.propertyName}>
<span>
{element.type}
</span>
<span>
{element.propertyName}
</span>
</div>
)
}
You can use the Javascript array map method to map each item in your property state into an HTML element.
For example:
Make a function that returns the mapped property state into HTML elements.
const renderProperties = () => {
return property.map((item, index) => (
// `item` is a representation of each of your object in the property array
// In this case, item contains { type: string, propertyName: string }
<div key={index}> // React requires user to put a key in each of the mapped component
<p>{item.propertyName}</p>
<p>{item.type}</p>
</div>
))
}
Call this function inside the HTML part of your JSX.
...
<button
className='buttonAccount'
type='submit'
onClick={handleSubmit}
>
Add Property
</button>
</div>
{renderProperties()} // <-- Here
</div>
https://reactjs.org/docs/lists-and-keys.html

Categories

Resources