React dropdown only show value if clicked on specific data using e.target? - javascript

So I'm trying to make this accordion and right now it maps through my data and essentially shows all the dropdown values when I click on my h1, but I only want it to show the dropdown for the specific h1 I clicked and not display the entire values
const Accordion = () => {
const [clicked, setClicked] = useState(false);
const toggle = e => {
if (e.target) {
setClicked(!clicked);
}
console.log(e.target);
};
return (
<div>
{Data.map((item, index) => {
return (
<div key={index}>
<h1 onClick={e => toggle(e)}>{item.question}</h1>
{clicked ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
</div>
);
})}
</div>
);
};
My toggle function shows the correct e.target value of the h1 I clicked on, but I don't know how to only display that h1 value when I click on it instead of showing all h1's values
Here is my data file
export const Data = [
{
question: 'What 1?',
answer: 'answer 1'
},
{
question: 'What 2',
answer: 'answer 2'
},
{
question: 'What 3?',
answer: 'answer 3'
}
];
How would I refactor my code to only show question 1 and answer 1, vs showing all questions and answers on click?

You need to track which div has been clicked . You can do that assigning id in your data like this:
const Data = [
{
question: "What 1?",
answer: "answer 1",
id: 0
},
{
question: "What 2",
answer: "answer 2",
id: 1
},
{
question: "What 3?",
answer: "answer 3",
id: 2
}
];
Then send this id on toggle method and check if id is equal to selected one or not like this:
const toggle = (e, id) => {
if (e.target) {
setSelected(id === selected ? null : id); //selected and id are same it means close the toggle
}
console.log(e.target);
};
and inside render check like this:
{selected === item.id ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
Here is full demo and code: https://codesandbox.io/s/toggle-accordian-eplpw

My suggestion would be to a separate component with its own state and use that for each accordion item.
const AccordionItem = (props) => {
const [clicked, setClicked] = useState(false);
return (
<div>
<h1 onClick={() => setClicked(! clicked)}>{props.question}</h1>
{clicked && (
<div>
<p>{item.answer}</p>
</div>
)}
</div>
);
};
const Accordion = () => {
return (
<div>
{Data.map((item, index) => {
return (
<AccordionItem
key={index}
question={item.question}
answer={item.answer}
/>
);
})}
</div>
);
};

You have to put the clicked question in state rather than just whether something was clicked or not. E.g. https://codesandbox.io/s/summer-night-4uulw?file=/src/App.js
const Data = [
{
question: 'What 1?',
answer: 'answer 1',
},
{
question: 'What 2',
answer: 'answer 2',
},
{
question: 'What 3?',
answer: 'answer 3',
},
];
const Accordion = () => {
const [activeQn, setActiveQn] = useState(null);
const toggle = ( index ) => {
// If the clicked qn is already active, then collapse it
if ( activeQn === index ) {
return setActiveQn(null)
}
// Otherwise show the answer to the clicked qn
setActiveQn(index)
};
return (
<div>
{Data.map((item, index) => {
return (
<div key={index}>
<h1 onClick={() => toggle(index)}>{item.question}</h1>
{activeQn === index ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
</div>
);
})}
</div>
);
};
That way, only the clicked answer will show and if the user wishes to collapse the answer, that can also be done.

Related

Use filter to only show the corresponding data in react

When the user clicks on a list item, I want it to render the paragraph content for that list item. When the user clicks on another list item, I then want to erase the previous content and register then new paragraph data for the new list item. Currently, when I click the list items, the info data stays for all and never goes away. Does this make sense? If you go to this website https://brittanychiang.com/ and click the "experience" button in the nav, you can see the functionality I am going for.
Questions :
import SingleQuestion from "./SingleQuestion";
import classes from "./Question.module.css";
const questions = [
{
id: 1,
title: "Step 1",
info: "paragraph 1 text",
},
{
id: 2,
title: "Step 2",
info: "paragraph 2 text",
},
{
id: 3,
title: "Step 3",
info: "paragraph 3 text",
},
{
id: 4,
title: "Step 4",
info: "paragraph 4 text",
},
];
const Questions = () => {
return (
<main>
<h1 className="infoTitle">We have answers</h1>
<div className={classes.container}>
{questions.map((question) => {
return <SingleQuestion key={question.id} {...question} />;
})}
</div>
</main>
);
};
export default Questions;
SingleQuestion:
import React, { useState } from "react";
import classes from "./Question.module.css";
const SingleQuestion = ({ title, info }) => {
const [text, setText] = useState("");
const showTextHandler = () => {
setText(info);
};
return (
<section className={classes.elementBin}>
<ul>
<li className={classes.hi} onClick={showTextHandler}>
{title}
</li>
</ul>
<p>{text}</p>
</section>
);
};
export default SingleQuestion;
There are plenty of ways of solving this, I will just show an example that does not differ so much from your current code:
const Questions = () => {
const [selectedId, setSelectedId] = useState();
return (
<main>
<h1 className="infoTitle">We have answers</h1>
<div className={classes.container}>
{questions.map((question) => {
return (
<SingleQuestion
key={question.id}
isSelected={question.id === selectedId}
setSelectedId={setSelectedId}
{...question}
/>
);
})}
</div>
</main>
);
};
const SingleQuestion = ({ id, title, info, isSelected, setSelectedId }) => {
return (
<section className={classes.elementBin}>
<ul>
<li className={classes.hi} onClick={() => setSelectedId(id)}>{title}</li>
</ul>
{isSelected && <p>{info}</p>}
</section>
);
};
As you can see, an upper state was created, holding the selected question id.
The setter for that state is passed to the SingleQuestion component.
Now that component does not hold an internal state anymore, it just update the selected question id, and shows its info just if is the selected one.

in a map function, i want to set a state which let me change the background color when i click on a div

So here's my problem, i map some data i receive from the back, it returns a group of div, i would like to be able to click a div, change his color background and use it in total price (they're options you can choose).
i tried to put a state "clicked" which set true on click, but the state is on all element ans not the only one i just clicked. After if my state is true, i change the background color and add it to the total price (calculated in the modal in details)
<p className="title-config">Configuration</p>
{data &&
data.additionalCharges.map((charges, index) => {
// console.log("charges.map", charges);
return (
<div
className={
clicked === true ? "clicked-config" : "unclicked-config"
}
key={index}
onClick={() => setClicked(true)}
>
<p>{charges.title}</p>
<p>{charges.description}</p>
<p>
{charges.price.amount} {location.state.price.currency}
</p>
</div>
);
})}
</div>
<div className="colonne2-config">
<div>
<span> Total {location.state.total}</span>
<span>{location.state.price.amount}</span>
</div>
<div>
<div onClick={() => setShowModal(true)}>Voir les details du prix</div>
<Modal
isOpen={showModal}
onRequestClose={() => setShowModal(false)}
style={{
overlay: {
backgroundColor: "lightgrey",
backgroundOpacity: "50%",
},
}}
>
<h1>Details du prix</h1>
<button onClick={() => setShowModal(false)}> X </button>
</Modal>
</div>
Here is a working example to achieve the desired objective:
Code Snippet
const {useState} = React;
const SomeComponent = ({data, ...props}) => {
// the clicked is being used to achieve two goals
// 1. track which item is clicked (ie, selected)
// 2. update the total-price by adding / subtracting the clicked item's price
// NOTE: This is not a good approach to employ in general. Please avoid.
// Instead, use a separate variable to calculate the total-price.
const [clicked, setClicked] = useState({total: 0});
const getClass = idx => (`item ${clicked[idx] ? 'selected' : 'unselected'}`);
return (
<div>
<h4>List of Items</h4>
{
data && Array.isArray(data) && data.map(
({title, description, amount}, idx) => (
<div
key={idx}
onClick={() => setClicked(prev => ({
...prev,
total: (
prev[idx] ? prev.total - +amount : prev.total + +amount
),
[idx]: !prev[idx]
}))}
class={getClass(idx)}
>
{title}   {description}   {amount}
</div>
)
)
}
<br/>
Total Price: {clicked.total}
</div>
);
};
const rawData = [
{title: 'Title 00', description: 'Description 00', amount: '100'},
{title: 'Title 01', description: 'Description 01', amount: '110'},
{title: 'Title 02', description: 'Description 02', amount: '120'},
{title: 'Title 03', description: 'Description 03', amount: '130'},
{title: 'Title 04', description: 'Description 04', amount: '140'},
{title: 'Title 05', description: 'Description 05', amount: '150'},
{title: 'Title 06', description: 'Description 06', amount: '160'}
];
ReactDOM.render(
<div>
<h2>DEMO</h2>
<SomeComponent data={rawData}/>
</div>,
document.getElementById('reactdiv')
);
.item {
border: 2px solid black;
margin-bottom: 10px;
padding: 2px 15px;
cursor: default;
width: fit-content;
}
.unselected { background-color: #EEEEFF; }
.selected { background-color: #6666AA; color: white}
<div id='reactdiv'/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
The clicked needs to be a data-structure that can track which of the items rendered are clicked (ie, selected) and which are not.
In this snippet, it is set as an object
For simplicity of the demo, the same clicked object serves a secondary purpose of holding the total price.
When user clicks on any item, it's background color changes (using getClass method)
And, the price of the item is added to or removed from total
Overall - this is a fairly simple, straight-forward code snippet.
As per my understanding of the question what you can do is, add another state called divIndex and change the condition to clicked && divIndex === index or you can just remove the clicked state and only use the divIndex state also like divIndex === index.
so i arrived to some kind of solution but it's not perfect, in my newOption, it does not remove the element i clicked on sometimes... it works perfect to add an element in the tab though...
here what i did.
const [selectedOption, setSelectedOption] = useState([]);
const [isSelected, setIsSelected] = useState(false);
const handleConfigClick = (charges, index) => {
const newOption = [...selectedOption];
// Est-ce que l'option est déjà présente ?
// console.log("index", index);
const exist = newOption.find((elem) => elem.id === charges.id);
// console.log("L'élément trouvé ====> ", exist);
if (exist) {
newOption.splice(index, 1);
setIsSelected(false);
console.log("exist");
} else {
console.log("existe pas");
setIsSelected(true);
newOption.push(charges);
}
console.log("newoption", newOption);
setSelectedOption(newOption);
};
it's probably not opti but it's close to my result, just need to resolve the case when the option does not leave the tab on click

Push specific object into list using id React

I got a list of objects which im displaying on the screen with the help of .map function.
It looks like this:
Component 1:
let itemList = [
{
type: "White T-shirt",
id: 1,
cost: 300,
image: whiteTshirt
},
{
type: "Purple T-shirt",
id: 2,
cost: 350,
image: purpleTshirt
},
{
type: "Baseballcap",
id: 3,
cost: 150,
image: whiteCap
},
{
type: "Vice Golfball",
id: 4,
cost: 40,
image: golfball
},
{
type: "Mousepad",
id: 5,
cost: 200,
image: mousepad
}
];
let products = itemList.map(items => {
let item =
<div key={items.id}>
<h2>{items.type}</h2>
<img className="image" src={items.image}></img>
<p className="price">${items.cost}</p>
<button onClick={onBuy} className="buy-btn">Buy</button>
</div>
return item;
})
return(
{shoppingcart ? <Component2 /> : null}
<main> {products} </main>
)
Component 2:
const Comopnent2 = props => {
const [webshop, setWebshop] = useState(false);
return(
<div>
{webshop ? <Webshop /> : null }
<a href="/Webshop" onClick={e => { e.preventDefault(); setWebshop(true)}} >
<p className="to-shop"> Back to shop</p></a>
<h2 className="shopping-header">Your Shopping Cart</h2>
<div className="cart-container">
// Here i want to object that i clicked display
</div>
)
}
What i want is to push one specific object to another array in another component that i have. I want to do that when i click the button which calls the onBuy funcion. How do i manage that? Thanks.
First create a hook for the cart item inside component 1:
const [cartItem, setCartItem] = useState();
set cartItem whenever the button is clicked and onBuy is called:
onBuy (id) {
let checkoutItem = this.itemList.find(item => item.id === id)
setCartItem(checkoutItem)
}
You'll be required to pass item id when you declare the button which call onBuy function.
let products = itemList.map(items => {
let item =
<div key={items.id}>
<h2>{items.type}</h2>
<img className="image" src={items.image}></img>
<p className="price">${items.cost}</p>
<button onClick={onBuy(item.id)} className="buy-btn">Buy</button>
</div>
return item;
})
To pass this selection to component 2. You can pass it as a prop in component 1:
return(
{shoppingcart ? <Component2 item={cartItem} /> : null}
<main> {products} </main>
In the component 2 You can display the data accordingly from props:
const Comopnent2 = props => {
const [webshop, setWebshop] = useState(false);
return(
<div>
{webshop ? <Webshop /> : null }
<a href="/Webshop" onClick={e => { e.preventDefault(); setWebshop(true)}} >
<p className="to-shop"> Back to shop</p></a>
<h2 className="shopping-header">Your Shopping Cart</h2>
<div className="cart-container">
{prop.item.name} //whatever properties your cart item has, I have used name just for example
</div>
)
}

Multiple array check react js

I have an array of objects which I'm rendering by section - see title of each object "Price", "Sectors and Charges" etc.
This populates a mini modal where users can select options to update rendered columns basically a filter.
The selection of the items are working however if I make a selection of the first item "0" all sections with the first option are selected.
How can I store the selection from each object into the selectedOptions array?
Please note I'm using react js and styled components, I've not added the styled component code.
Data:
const columnsData = [
{
title: 'Price',
options: [
{
label: 'Daily Change'
},
{
label: 'Price'
},
{
label: 'Price Date'
},
{
label: 'Volatility Rating'
}
],
},
{
title: 'Sectors and Charges',
options: [
{
label: 'Sector'
},
{
label: 'Asset Class'
},
{
label: 'AMC'
},
],
},
{
title: 'Cumulative Performance',
options: [
{
label: '1 month'
},
{
label: '6 months'
},
{
label: '1 year'
},
],
},
]
Code:
const EditColumns = ({active, onClick}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const update = () => {
onClick();
}
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList>
{item.options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionList>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
export default EditColumns;
Your section lists are all sharing the same state variable, so any changes will be applied to all of them. You could fix this either by constructing a more complex state object which more closely resembles the structure of columnsData, or making each SectionList its own component with its own state. What you decide to do will depend on the degree to which the EditButtons component actually needs access to the whole state.
The second approach might look something like this:
const EditColumns = ({active, onClick}) => {
const update = () => {
onClick();
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList options={item.options}/>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
const SectionList = ({options}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<SectionListContainer>
{options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionListContainer>
)
}

Set state for only for self data into map on reactjs

I have a object's array of users and i'm using map to show them, each user have a option buttons that is 'edit' and 'remove' options each option have a onlclick function that set a state to show another view so the code explain itselft
class App extends React.Component {
state = {
edit: false,
remove: false
}
handleEdit = () => {
this.setState({ edit: true })
}
handleRemove = () => {
this.setState({ remove: true })
}
cancelEdit = () => {
this.setState({ edit: false })
}
cancelRemove = () => {
this.setState({ remove: false })
}
renderEditItem = () => {
const {
state: {
edit,
remove
},
cancelEdit,
cancelRemove,
handleEdit,
handleRemove
} = this
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
if (remove) {
return (
<div>
<span>Remove view</span>
<br/>
<button onClick={cancelRemove}>Cancel</button>
</div>
)
}
return (
<div>
<button onClick={handleEdit}>Edit</button>
<br/>
<button onClick={handleRemove}>Remove</button>
</div>
)
}
renderUsers = () => {
const {
renderEditItem
} = this
const users = [
{
id: 1,
name: 'User1'
},
{
id: 2,
name: 'User-2'
},
{
id: 3,
name: 'User-3'
}
]
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{renderEditItem()}
</div>
</li>
</ul>
)
})
}
render () {
return (
<div>
{this.renderUsers()}
</div>
)
}
}
React.render(
<App />,
document.getElementById('app')
);
JSfiddle: Here
The issue is how can you see is, when i click on the button to set the state for edit or remove option, this will show the view for all the items,
and should be only the view that is clicked, i know the state change to true and is the same for all the items but i don't know how to set the state only for one entry any idea?
Thank you in advance.
Your problem is that the edit/remove state is singular and for the entire list. Each item in the list receives the same state here:
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
The single edit variable from the state is applied to each list item. If you want to individually set the edit state for each item, it will need to be kept track of with that item.
EX:
const users = [
{
id: 1,
name: 'User1',
edit: true
}]
This way each individual item will be able to tell what state it is in individually. User1 item will have an edit mode that is independent of the other users.
Then you can render something like this:
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{user.edit ? 'EDIT MODE' : 'NOT EDIT MODE'}
</div>
</li>
</ul>
)
})

Categories