Toggle button inside map - reactjs

I'm trying to set a toggle inside a map , it activates with a button , now i handled to difference between every single item of the card but there's one problem, since the state changes, i mean i'm not updating it, the state doesn't let me have several buttons activated, i tried making the initial state an array of objects but i can't upload one single position of the array...
here's the code:
Handler
//hooks
const [detail, setDetail] = useState([{
id: '',
state: false,
}]);
const handleClick = (e, id) => {
setDetail({ ...detail, id: id, state: !detail['state'] })
}
Map
<div className={Styles.wrapper}>
{
artistSales && artistSales.map(s => {
return (
<div className={Styles.align}>
<div className={Styles.card}>
<div className={Styles.order}>
<h2>Numero de orden: {s.id_order}</h2>
<p>Estado de pago: {s.state === 'fullfilled' && 'pago realizado'} </p>
<p>Cliente: {s.userId.name} {s.userId.lastname} </p>
<p>Email:
<a href={`mailto:${s.userId.email}`}>{s.userId.email}</a>
</p>
</div>
<div className={Styles.detail}>
<p>Total: ${s.total_price}</p>
<p>Fecha: {s.createdAt.slice(0, 10)}</p>
<button value={s.id_order} onClick={(e) => handleClick(e, s.id_order)} className={Styles.btn}>Ver detalles</button>
</div>
</div>
{detail.id === s.id_order && detail.state === true && <div>
hello
</div>}----> this is what should be displayed when you click the button

you need to first find a matching id. if there is none you should add it to array, otherwise make a copy from the match with new state value.
you can create also a function to check if for a given id that specific state value to render your content logic:
const [details, setDetails] = useState([]);
const handleClick = (e, id) => {
setDetails(details => {
// find index for matching detail
const detailIndex = details.findIndex(d => d.id === id)
// no match return new state
if (detailIndex === -1) {
return [...details, { id, state: true }]
}
// with match create copy details
const nextDetails = [...details]
// create copy of detail with a flipped state value
const state = !nextDetails[detailIndex].state
nextDetails[detailIndex] = { ...nextDetails[detailIndex], state }
return nextDetails
})
}
// function to get artist detail state
const detailForArtist = ( id ) => {
const detail = details.find(d => d.id === id)
return detail?.state
}
{ detailForArtist(s.id_order) && <div>hello</div> }

Related

Coloring the appropriate item from the list after clicking. [Next Js]

I want to create a function that will color the hearts when clicked.
I wrote a function that prints out elements for me, but when I click on any heart, it colors them all.
Where could the problem be?
My code:
const \[userInput, setUserInput\] = useState("");
const \[list, setList\] = useState(\[\]);
const \[hearth, setHearth\] = useState(false);
const \[active, setActive\] = useState(-1);
const handleChange = (e) =\> {
e.preventDefault();
setUserInput(e.target.value);
};
const handleSubmit = (e) =\> {
e.preventDefault();
setList(\[userInput, ...list\]);
setUserInput("");
};
const wishList = (e) =\> {
setHearth(!hearth);
};
useEffect(() =\> {}, \[userInput, list\]);
return (
\<div className="favMusic"\>
<h1>FavMusicList</h1>
\<form\>
\<input value={userInput} onChange={handleChange} type="text" /\>
\<button onClick={handleSubmit}\>Submit\</button\>
\</form\>
<ul className="favMusic__list">
{list.map((i, idx) => {
console.log(idx);
return (
<li key={idx}>
{i}{" "}
<div
id={idx}
onClick={() => wishList(idx)}
className={"hearth" + " " + (hearth ? "true" : "false")}>
<AiOutlineHeart
/>
</div>
</li>
);
})}
</ul>
</div>
I have tried all possible ways from setState to others found on the net but I have no idea how to solve it
Here's a working demo.
Assuming your state data is an array of items, each with its own boolean property indicating whether it's been "liked" by the user:
[
{
id: 1,
liked: true,
title: 'ListItem 1',
},
{
id: 2,
liked: false,
title: 'ListItem 2',
},
// ...
]
Then in your click handler, you'd want to loop over each of the objects to find the item with the corresponding id to change just the boolean property for that one item. For example:
const handleClick = (id) => {
const newLikes = items.map((item) => {
// check the current element's id against the
// id passed to the handler
if (item.id === id) {
// if it matches, update the liked property
// and return the modified object
return { ...item, liked: !item.liked };
}
// if it doesn't match, just return the
// original object
return item;
});
// update state with the new data
setItems(newLikes);
};

Map and If statement unexpected behaviour

The results I want to achieve are:
If the entered name is already on the array "persons", show an alert "Name already exist".
The code is not working, because I keep on getting the alert message all the time.
I think the condition comparison variable (personObject.name ) is wrong.
Can someone explain how this should be done?
Without the condition, the names ad added propperly into the array.
//Condition
if (persons.map((person) => person.name === personObject.name) )
{
alert("Name already exist");
}
else
{
//*/
setPersons(persons.concat(personObject))
setNewName('')
console.log(persons)
}
Code sandbox
Full code:
const App = () => {
//Reminder: current state, function that updates it, initial state.
const [persons, setPersons] = useState([
//The array persons is empty at start
]);
const [newName, setNewName] = useState('');
//adding new persons
const addPerson = (event) => {
event.preventDefault();
/* complete the addPerson function for creating new persons */
const personObject = {
name: newName,
id: persons.length + 1,
};
//Condition
if (persons.map((person) => person.name === personObject.name)) {
alert('Name already exist');
} else {
//*/
setPersons(persons.concat(personObject));
setNewName('');
console.log(persons);
}
};
const handlePersonChange = (event) => {
console.log(event.target.value);
setNewName(event.target.value);
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addPerson}>
<div>
name:
<input value={newName} onChange={handlePersonChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
{console.log(persons)}
<ul>
{persons.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
</div>
);
};
export default App;
You are checking condition with using map and map will return new array so if condition will be always true.
So instead of map you should use some like below:-
persons.some((person) => person.name === personObject.name);
You can use the filter method that filters an array and returns you an array with items matching the condition, if this new array is empty it means no items matches it :
let alreadyExists = persons.filter(person => person.name === personObject.name)
if (alreadyExists.length > 0) {
alert("Name already exist")
} else {
setPersons(persons.concat(personObject))
setNewName('')
console.log(persons)
}

Cannot remove inputs array with filter

I am trying to remove an input field with filter function but it's not working.
In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can achieve removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders
So when I refresh the page and click to remove an input it will clear all other input data. How can I fix this problem ?
Update adding full component in question:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rules.rule_0,
rule_1: teamData.rules.rule_1,
rule_2: teamData.rules.rule_2,
rule_3: teamData.rules.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(
teamData.rules.map((el) => ({
...el,
guid: uuidV4(),
}))
);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule_${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const removeInputs = (index) => {
const newList = inputs.filter((item, i) => index !== i); // <-- compare for matching index
setInputs(newList);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
// setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
// console.log("teamData.rules", teamData);
console.log("inputs", inputs);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={data.guid}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
When i do console.log(inputs) this is the data that I got:
0: 0: "t" 1: "e" 2: "s" guid: "e18595a5-e30b-4b71-8fc2-0ad9c0e140b2"
proto: Object 1: 0: "d" 1: "a" 2: "s" 3: "d" 4: "a" 5: "s" guid: "537ca359-511b-4bc6-9583-553ea6ebf544" ...
Issue
The issue here is that you are using the array index as the React key. When you mutate the underlying data and reorder or add/remove elements in the middle of the array then the elements shift around but the React key previously used doesn't move with the elements.
When you remove an element then all posterior elements shift forward and the index, as key, remains the same so React bails on rerendering the elements. The array will be one element shorter in length and so you'll see the last item removed instead of the one you actually removed.
Solution
Use a React key that is intrinsic to the elements being mapped, unique properties like guids, ids, name, etc... any property of the element that guarantees sufficient uniqueness among the dataset (i.e. the siblings).
const [inputs, setInputs] = useState(teamData.rules);
const removeInputs = (index) => {
// compare for matching index
setInputs(inputs => inputs.filter((item, i) => index !== i));
};
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={data.id}> // <-- use a unique property
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
If your teamData.rules initial state value doesn't have any unique properties to use then you can map this to a new array and add a sufficient id property.
const [inputs, setInputs] = useState(teamData.rules.map(el => ({
...el,
guid: generateId()***,
})));
*** this is a function you need to define yourself, or import from a module like uuid***
import { v4 as uuidV4 } from 'uuid';
...
const [inputs, setInputs] = useState(teamData.rules.map(el => ({
...el,
guid: uuidV4(),
})));
// Add more input
const addInputs = () => {
setInputs(inputs => [
...inputs,
{
name: `rule_${inputs.length + 1}`,
guid: uuidV4();
},
]);
};
Then when mapping use the guid property.
<div className="agreement-form-grid" key={data.guid}>
The issue is because you are trying to compare index with array item in filter method. You should use the second argument in filter which denotes the array index of the current iterating item
const removeInputs = (index) => {
const newList = inputs.filter((item,i) => index !== i);
setInputs(newList);
};
That's your solution, you are trying with item but you are comparing it with index that's wrong. You should do it like this,
const newList = inputs.filter((item, key) => index !== key);

React Hooks - Remove Specific Item from an array using Index

I have two arrays,
const [imagesUrlArray, setURls] = useState([])
const [imagesArray, setImages] = useState([])
using handle change below; imagesUrlArray is used to display the images on the screen, and imagesArray is saved to later update those same images to the database
const handleChange = (e) => {
let selected = e.target.files[0]
var selectedImageSrc = URL.createObjectURL(selected);
addUrl(selectedImageSrc)
addImage(selected)
};
Though I now want to click the X(delete) button and remove the item at index of imagesUrlArray and imagesArray (say if the user no longer wants to use that image)
<div className="img-grid">
{ imagesUrlArray && imagesUrlArray.map((url,index) => {
return ( <div key={index}
className="img-wrap">
{/* <span class="close">×</span> */}
<button onClick={ () => handleRemove(index)} className="close">X</button>
<img src={url} alt="uploaded" />
</div>
)
}
)}
</div>
I have tried splice and slice etc but still cannot find a perfect solution,
here is the handleRemove Function
const handleRemove = (index) => {
const newImagesArray = imagesArray.splice(index);
const newImagesUrlArray = imagesUrlArray.splice(index);
setImages(newImagesArray);
setURls(newImagesUrlArray);
}
You can do something like this:
const handleRemove = (index) => {
setImages(imagesArray.filter((x,i) => i !== index));
setURls(imagesUrlArray.filter((x,i) => i !== index));
}
So, basically the idea is to skip the element at specific index and return the remaining items and set the state.

React Hook [useState]: update state and rerender the view

Experience: I am a total beginner in React.
What I am trying to learn: Hooks (useState) - but I do not know how to update the state and rerender the view with this. As far as I understood React does not rerender the view if the updated state is somewhat similar to the last one... After googling, I tried to copy the state and update it somehow, but I am missing something, and I do not know what.
What I am trying to do in the project: I have a list of countries I want to filter through when the user selects a region from a dropdown. This is the function that gets fired when the selection happens, along with comments that I hope explain what I am trying to do:
const change = event => {
//copy the `data` state (which has a list of all the countries)
let newData = [...data];
console.log(newData);
//filter through the countries list to get only those with the selected region
let filtered = newData.filter(obj => obj.region === event.target.value);
console.log(filtered);
//change the countries list with the filtered one, and rerender the view
setData([data, newData]);
console.log(data);
};
You can find the file and the code in question HERE (scroll down to get to the change function)
Select a region from the 'Fitler by region dropdown'
See the errors/outputs in the console
You are updating the state to an array of objects and the last item will be the filtered list
Instead, pass in a single array that holds the filtered countries.
Note that your state will be lost the second time you select a different region because you are modifying the entire collection of countries.
setData(data.filter(obj => obj.region === event.target.value))
So what you can we to avoid losing the state?
We can filter the list based on the selected region.
Added comments where i changed the code
export default function CountriesList() {
const [data, setData] = useState([]);
const [distinctRegions, setDistinctRegions] = useState([]);
const [loading, setLoading] = useState(true);
// added state to track the selected region
const [selectedRegion, setSelectedRegion] = useState("");
useEffect(() => {
CountriesAPI().then(res => {
onLoad(res);
setLoading(false);
});
}, []);
const onLoad = dataList => {
setData(...data, dataList);
getRegions(dataList);
};
const getRegions = dataList => {
let regions = [];
dataList.map(dataItem =>
dataItem.region.length ? regions.push(dataItem.region) : ""
);
let regionsFiltered = regions.filter(
(item, index, arr) => arr.indexOf(item) === index
);
setDistinctRegions(...distinctRegions, regionsFiltered);
};
const renderLoading = () => {
return <div>Loading...</div>;
};
// now we only need to update the selected region
const change = event => {
setSelectedRegion(event.target.value);
};
const renderData = (dataList, distinctRegionsItem) => {
if (dataList && dataList.length) {
return (
<div>
<Container>
<Input type="text" placeholder="Search for a country..." />
<Select className="select-region" onChange={change}>
<option value="" hidden>
Filter by region
</option>
// added show all
<option value="">Show All</option>
{distinctRegionsItem.map(item => {
return (
<option key={item} value={item}>
{item}
</option>
);
})}
</Select>
</Container>
<CardList>
// filter the array based on selectedRegion and then render the list.
// if selectedRegion is empty show all
{dataList
.filter(
country => !selectedRegion || country.region === selectedRegion
)
.map(country => (
<CountryCard
population={country.population}
region={country.region}
capital={country.capital}
flag={country.flag}
key={country.alpha3Code}
id={country.alpha3Code}
name={country.name}
/>
))}
</CardList>
</div>
);
} else {
return <div>No items found</div>;
}
};
return loading ? renderLoading() : renderData(data, distinctRegions);
}

Resources