I am using an array of objects to loop some services in my webpage. The data is
data.js
const data = [
{ type: "Mirror Cleaning", price: "29" },
{ type: "Ceiling Fan Cleaning", price: "39" },
{ type: "Window Cleaning", price: "139" },
{ type: "Toilet Seat Stain Removal", price: "109" },
{ type: "Wash Basin Cleaning", price: "49" },
{ type: "Exhaust Fan Cleaning", price: "79" },
{ type: "Kitchen Sink Cleaning", price: "79" },
{ type: "Gas Stove (HOB) Cleaning", price: "89" },
];
export default data;
I added a button in my app.js where I can select and unselect the data which I added to the service array. But the problem is whenever I select a service in my app all button name changed from Select to Unselect which I don't want. I want to achieve only the item that I clicked its button changed. But all the buttons in the window change. I know where is the problem but I don't know how to achieve what I want to. Here is the code of my app.js.
App.js
import "./styles.css";
import React from "react";
import data from "./data";
export default function App() {
const [service, setService] = React.useState([]);
const [display, setDisplay] = React.useState(true);
function Select(type, price) {
setService((prevItems) => [...prevItems, { type: type, price: price }]);
setDisplay(false);
}
function Unselect(type, price) {
setService((prevItems) => prevItems.filter((data) => data.type !== type));
setDisplay(true);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{data.map((data) => (
<>
<div key={data.type} style={{ height: "80px" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
{display ? (
<button
onClick={() => Select(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Select
</button>
) : (
<button
onClick={() => Unselect(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Unselect
</button>
)}
</div>
</>
))}
<p> Selected Services comes here </p>
{service.map((data) => (
<>
<div
key={data.type}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "50px"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
</>
))}
</div>
);
}
Here is the link of the codesandbox that I checked the code.
I want that only the particular service that is clicked its button change from select to unselect but all of them are changing. please help.
CodePudding user response:
There's a lot of redundant code; but thats alright, there's always room for improvement, but good that you attempted!
I've refactored your code with minimal code. You can checkout the sandbox here.
What I've improved is:
- Unnecessary use of multiple on change functions (select / unselect).
- Unnecessary use of conditional rendering - I've merged it into one.
import "./styles.css";
import React from "react";
import data from "./data";
export default function App() {
const [selectedServices, setSelectedService] = React.useState([]);
function Select(type, price) {
// Check if selected service is already selected
if (!selectedServices.map((item) => item.type).includes(type)) {
// Add to array
setSelectedService((prevItems) => [
...prevItems,
{ type: type, price: price }
]);
} else {
// remove from array!
const allOtherServices = selectedServices.filter(
(item) => item.type !== type
);
setSelectedService([...allOtherServices]);
}
}
// I want that only the particular service that is clicked its button change from selcet to unselect but all of them are changing
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{data.map((data) => (
<>
<div key={data.type} style={{ height: "80px" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
<button
onClick={() => Select(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
{!selectedServices.map((item) => item.type).includes(data.type)
? "Select"
: "Unselect"}
</button>
</div>
</>
))}
<p> Selected Services comes here </p>
{selectedServices.map((data) => (
<>
<div
key={data.type}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "50px"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
</>
))}
</div>
);
}
CodePudding user response:
your display
is always the same for all of your data item. If you want to select only 1 item at a time, you should define a typeToDisplay
in your component's state to hold the type of the item you want to display. Then, conditionally display the item by comparing typeToDisplay
with data.type
import "./styles.css";
import React from "react";
import data from "./data";
export default function App() {
const [service, setService] = React.useState([]);
const [typeToDisplay, setTypeToDisplay] = React.useState("");
function Select(type, price) {
setService((prevItems) => [...prevItems, { type: type, price: price }]);
setTypeToDisplay(type);
}
function Unselect(type, price) {
setService((prevItems) => prevItems.filter((data) => data.type !== type));
setTypeToDisplay("");
}
// I want that only the particular service that is clicked its button change from selcet to unselect but all of them are changing
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{data.map((data) => (
<>
<div key={data.type} style={{ height: "80px" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
{typeToDisplay === data.type ? (
<button
onClick={() => Unselect(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Unselect
</button>
) : (
<button
onClick={() => Select(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Select
</button>
)}
</div>
</>
))}
<p> Selected Services comes here </p>
{service.map((data) => (
<>
<div
key={data.type}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "50px"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
</>
))}
</div>
);
}
CodePudding user response:
you can use index for this purpose as uniquely. Pass index to both select and unselect function index and then set this value to display and in rendering you again match display value with index. It will automatically work according to requirements as I have used your code for the same and it's working fine below.
import "./styles.css";
import React from "react";
import data from "./data";
export default function App() {
const [service, setService] = React.useState([]);
const [display, setDisplay] = React.useState('');
function Select(type, price, index) {
console.log(type, price, index);
setService((prevItems) => [...prevItems, { type: type, price: price }]);
setDisplay(index);
}
function Unselect(type, price, index) {
setService((prevItems) => prevItems.filter((data) => data.type !== type));
setDisplay(index);
}
// I want that only the particular service that is clicked its button change from selcet to unselect but all of them are changing
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{data.map((data, index) => (
<>
<div key={data.type index} style={{ height: "80px" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
{display === index ? (
<button
onClick={() => Select(data.type, data.price, index)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Select
</button>
) : (
<button
onClick={() => Unselect(data.type, data.price, index)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Unselect
</button>
)}
</div>
</>
))}
<p> Selected Services comes here </p>
{service.map((data) => (
<>
<div
key={data.type}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "50px"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
</>
))}
</div>
);
}
Please let me know any queries. Thanks
CodePudding user response:
What you are doing is using a single piece of state for different elements in your data array. You have to track it individually for every data item. I am going to demonstrate with code, as it is hard to explain and the answer will be very long. Please read the code comment to understand what you have to change.
import "./styles.css";
import React, { useEffect} from "react";
import data from "./data";
export default function App() {
// First create a new state based on your data by mapping through them individually
// and setting a new property name selected
const [dataState, setDataState] = React.useState(() => data.map(item => ({
...item,
selected: false
})));
const [service, setService] = React.useState([]);
function Select(type, price) {
setService((prevItems) => [...prevItems, { type: type, price: price }]);
// set that particular item's selected property to true when selected.
setDataState((st) => {
return st.map(item => {
if(item.type === type) {
return {
...item,
selected: true
}
}
return item
})
})
}
function Unselect(type, price) {
setService((prevItems) => prevItems.filter((data) => data.type !== type));
// similar for unselect, set the selected property to false
setDataState((st) => {
return st.map(item => {
if(item.type === type) {
return {
...item,
selected: false
}
}
return item
})
})
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{/* Now map through that dataState instead of data */ }
{dataState.map((data) => (
<>
<div key={data.type} style={{ height: "80px" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
{!data.selected ? (
<button
onClick={() => Select(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Select
</button>
) : (
<button
onClick={() => Unselect(data.type, data.price)}
style={{
color: "white",
backgroundColor: "black",
padding: "10px",
outline: "0",
border: "none",
margin: "0px auto 0px 0px"
}}
>
Unselect
</button>
)}
</div>
</>
))}
<p> Selected Services comes here </p>
{service.map((data) => (
<>
<div
key={data.type}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "50px"
}}
>
<div>{data.type}</div>
<div>{data.price}</div>
</div>
</>
))}
</div>
);
}
CodePudding user response:
You can checkout the working example here.
Improvements:
- Unnecessary use of multiple on change functions (select / unselect).
- Unnecessary use of conditional rendering - I've merged it into one.
- Unnecessary loops
const data = [
{ type: 'Mirror Cleaning', price: '29' },
{ type: 'Ceiling Fan Cleaning', price: '39' },
{ type: 'Window Cleaning', price: '139' },
{ type: 'Toilet Seat Stain Removal', price: '109' },
{ type: 'Wash Basin Cleaning', price: '49' },
{ type: 'Exhaust Fan Cleaning', price: '79' },
{ type: 'Kitchen Sink Cleaning', price: '79' },
{ type: 'Gas Stove (HOB) Cleaning', price: '89' },
];
export default function App() {
const [services, setServices] = React.useState([]);
function toggle(idx) {
let tmp = [...services];
data[idx].display = !data[idx].display;
tmp[idx] = tmp[idx] ? undefined : data[idx];
setServices(tmp);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{data.map(
(d, idx) =>
d && (
<>
<div key={d.type} style={{ height: '80px' }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div>{d.type}</div>
<div>{d.price}</div>
</div>
<button
onClick={() => toggle(idx)}
style={{
color: 'white',
backgroundColor: 'black',
padding: '10px',
outline: '0',
border: 'none',
margin: '0px auto 0px 0px',
}}
>
{d.display ? 'UnSelect' : 'Select'}
</button>
</div>
</>
)
)}
<p> Selected Services comes here </p>
{services.map(
(service) =>
service && (
<>
<div
key={service.type}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
height: '50px',
}}
>
<div>{service.type}</div>
<div>{service.price}</div>
</div>
</>
)
)}
</div>
);
}