React setting multiple states dynamically - javascript

Im learning React and currently playing with passing data around.
I have info for 5 cards stored in state like this:
state = {
cards: [
{ id: 1, name: "p1", value: "Prize 1", imageSrc: "/path/to/image", bgColor: { background: "#FF6384", border: "4px solid #FF6384" } },
{ id: 2, name: "p2", value: "Prize 2", imageSrc: "/path/to/image", bgColor: { background: "#4BC0C0", border: "4px solid #4BC0C0" } },
{ id: 3, name: "p3", value: "Prize 3", imageSrc: "/path/to/image", bgColor: { background: "#FFCE56", border: "4px solid #FFCE56" }},
{ id: 4, name: "p4", value: "Prize 4", imageSrc: "/path/to/image", bgColor: { background: "#67bd42", border: "4px solid #67bd42" } },
{ id: 5, name: "p5", value: "Prize 5", imageSrc: "/path/to/image", bgColor: { background: "#c931f7", border: "4px solid #c931f7" }}
],
flipped: null,
};
I am displaying them with an iteration, like this:
<div className="prize-cards-inner">
{this.state.cards.map((card) => {
return (
<section key={card.id} className="prize-card-container">
<div
className={`prize-card ${
this.state.flipped === card ? "flipped" : ""
}`}
onClick={() => this.clickHandler(card)}
>
<div className="front">
<div className="card-name-div" style={ card.bgColor }>
{card.value}
</div>
<div className="prize-image-div" >
<img className="prize-image" src={card.imageSrc} alt="test" />
</div>
<div className="slot-name-div" style={card.bgColor}>
<p> {card.name}</p>
</div>
</div>
<div className="back">Prize</div>
</div>
</section>
);
})}
</div>
and handling card clicks with this:
clickHandler = (card) => {
if (this.state.flipped) {
return;
}
this.setState({
flipped: card,
***Set another state for all other cards (how do I say "set the state of all cards that aren't this card?")***
});
};
I would like to set the state for all other cards aside from the clicked card at the same time I set the state for the clicked card.
I have no clue how to do what I want, so I have marked the part I am unsure about with asterisks. Thanks in advance for your time.

I think you are looking for this:
clickHandler(card) {
if (this.state.flipped) {
return;
}
this.setState((state) => ({
flipped: card,
cards: state.cards.map((c) => (
// If the card we are mapping is not the flipped card add someOtherState to it
c === card ? c : { ...c, someOtherState: 'some value'};
)),
});
};
I'd recommend you start using functional components with React hooks right away. They provide a much nicer developer experience

Related

How can I create a menu with my json info with REACT

what I try to do is to have the same display as this picture :
So in my menu the plant type (Type of plant1) is displayed above a gray bar and when you click on the down chevron then you can see all the plants name, related to this type, with checkboxes on left, by default there will be all checked. And the blue rectangle indicates the number of plants that have been selected.
How can I do that, which package can help me in REACT?
Here my plants.json :
{
"plants_type": [
{
"_id_type": "1",
"name_type": "Type of plant1",
"plants": [
{
"name": "Plant1.1",
"_id": "2"
},
{
"name": "Plant1.2",
"_id": "3"
}
]
},
{
"_id_type": "4",
"name_type": "Type of plant2",
"plants": [
{
"name": "Plant2.1",
"_id": "5"
},
{
"name": "Plant2.2",
"_id": "6"
}
]
}
]
}
You can create a dropdown list on your own like below. I have added the logic of selecting items to the data itself.
You can keep a component called Category to keep a single state of the parent menu item. Whether it's open or not. Then iterate over the plants as checkbox inputs to make them selectable.
I have used a simple initialize function to make all the items selected initially. This should work as you expect. Add a console log of selectionMenu to see how selected property changes while toggling items.
Move the inline styles to CSS classes to make the code more clear.
const data = { plants_type: [ { _id_type: "1", name_type: "Type of plant1", plants: [ { name: "Plant1.1", _id: "2" }, { name: "Plant1.2", _id: "3" } ] }, { _id_type: "4", name_type: "Type of plant2", plants: [ { name: "Plant2.1", _id: "5" }, { name: "Plant2.2", _id: "6" } ] } ] };
const Category = ({ _id_type, name_type, plants, changeSelection }) => {
const [toggleState, setToggleState] = React.useState(false);
return (
<div key={_id_type}>
<div
style={{
cursor: "pointer",
userSelect: "none",
display: "flex",
margin: "2px",
backgroundColor: "lightgray"
}}
onClick={() => setToggleState((prev) => !prev)}
>
<div>{name_type}</div>
<div
style={{
backgroundColor: "blue",
color: "white",
padding: "0px 10px",
marginLeft: "auto"
}}
>
{plants.filter(({ selected }) => selected).length}
</div>
</div>
<div style={{ marginLeft: "10px" }}>
{toggleState &&
plants.map(({ name, _id, selected }) => (
<div key={_id}>
<input
key={_id}
type="checkbox"
value={name}
checked={selected}
onChange={(e) => changeSelection(_id_type, _id, e.target.value)}
/>
{name}
</div>
))}
</div>
</div>
);
};
const App = () => {
const initializeSelectionMenu = (data) => {
return data.map((item) => {
return {
...item,
plants: item.plants.map((plant) => ({ ...plant, selected: true }))
};
});
};
const [selectionMenu, setSelectionMenu] = React.useState(
initializeSelectionMenu(data.plants_type)
);
console.log(selectionMenu);
const changeSelection = (catId, itemId, value) => {
setSelectionMenu((prevSelectionMenu) =>
prevSelectionMenu.map((item) => {
if (item._id_type === catId) {
return {
...item,
plants: item.plants.map((plant) => {
if (plant._id === itemId) {
return { ...plant, selected: !plant.selected };
}
return plant;
})
};
}
return item;
})
);
};
return (
<div>
{selectionMenu.map((item) => (
<Category
{...item}
changeSelection={changeSelection}
key={item._id_type}
/>
))}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

How to Filter Quantity By Attribute in ReactJs

I'm attempting to create a quantity filter based on color, size, or both. When I click the red color, for example, it displays the whole quantity of the red color, but if I press color red and size small, it displays the exact quantity I require. Is there a way to accomplish this?
This is what I mean.
When I select a color or a size, the quantity should be displayed. Also, there should not be a duplication of my error, since there are three red colors listed above the image.
Code
import React, { useState } from "react";
export default function ControlledRadios() {
const [qty, setQty] = useState(0);
const data = [
{
id: 1,
name: "Product A",
attributes: [
{
id: 1,
color: "Red",
size: "Small",
qty: 200,
},
{
id: 2,
color: "Red",
size: "Medium",
qty: 100,
},
{
id: 3,
color: "Red",
size: "Large",
qty: 300,
},
{
id: 4,
color: "Yellow",
size: "Small",
qty: 200,
},
{
id: 5,
color: "Yellow",
size: "Medium",
qty: 100,
},
{
id: 6,
color: "Yellow",
size: "Large",
qty: 300,
},
],
},
];
const handleChange = (event) => {
setQty(event.target.value);
};
return (
<>
<h1>Quantity: {qty}</h1>
<fieldset value={qty} onChange={(event) => handleChange(event)}>
<h3>Color:</h3>
{data?.map(({ attributes }) => {
return attributes.map(({ id, ...rest }) => (
<>
<label key={id}>
<input
type="radio"
name="schedule-weekly-option"
value={rest.qty}
/>
{rest.color}
</label>
<br />
</>
));
})}
<h3>Size:</h3>
{data?.map(({ attributes }) => {
return attributes.map(({ id, ...rest }) => (
<>
<label key={id}>
<input
type="radio"
name="schedule-weekly-option"
value={rest.qty}
/>
{rest.size}
</label>
<br />
</>
));
})}
</fieldset>
</>
);
}
As seen in the data value, I have multiple color and sizes goes something like this for example {color: "Red", Size: "Small", qty: 200} I have multiple red values, so right every red should be added so when I click the Red radio button it should display the quantity 600 because the quantity of all the red will be added. but if I press like color Red and size Small it should display 200.
PS: If possible can u not make duplicate of colors like I did 3 color red and yellow, to make it only 1 Red and 1 Yellow same goes with the size.
If you need anymore clarification you need or explanation please comment down below. Thanks
Phew! This should do the trick. The idea is to pass more data to your handleChange handleChange - see how I changed it? I grab the data straight out of the target element, which is not ideal, but works.
I didn't do the filtering for you, good luck with that :) Should be just adding a (granted, a fairly complex) .filter(...) to your .map(...).
document.onreadystatechange = () => {
const {useState} = React;
function ControlledRadios() {
const [qty, setQty] = useState(0);
const data = [
{
id: 1,
name: "Product A",
attributes: [
{
id: 1,
color: "Red",
size: "Small",
qty: 200,
},
{
id: 2,
color: "Red",
size: "Medium",
qty: 100,
},
{
id: 3,
color: "Red",
size: "Large",
qty: 300,
},
{
id: 4,
color: "Yellow",
size: "Small",
qty: 200,
},
{
id: 5,
color: "Yellow",
size: "Medium",
qty: 100,
},
{
id: 6,
color: "Yellow",
size: "Large",
qty: 999,
},
],
},
];
const handleChange = (event) => {
const id = event.target.value;
const targetAttribute = data[0].attributes.find(x => x.id == id);
if (event.target.name === "schedule-weekly-option-color") {
let sum = 0;
data[0].attributes.forEach((a) => {
if (a.color===targetAttribute.color) {
sum += a.qty;
}
});
setQty(sum);
} else {
let sum = 0;
data[0].attributes.forEach((a) => {
if (a.color===targetAttribute.color && a.size===targetAttribute.size) {
sum += a.qty;
}
});
setQty(sum);
}
};
return (
<React.Fragment>
<h1>Quantity: {qty}</h1>
<fieldset value={qty} onChange={(event) => handleChange(event)}>
<h3>Color:</h3>
{data.map(({ attributes }) => {
return attributes.map(a => (
<label key={a.id}>
<input
type="radio"
name="schedule-weekly-option-color"
value={a.id}
/>
{a.color}
</label>
));
})}
<h3>Size:</h3>
{data.map(item => {
return item.attributes.map(a => (
<label key={a.id}>
<input
type="radio"
name="schedule-weekly-option-size"
value={a.id}
/>
{a.size}
</label>
));
})}
</fieldset>
</React.Fragment>
);
}
ReactDOM.render(<ControlledRadios />, document.body);
};
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>

How can i grab one of my elements in the array and style it?

Hi devs im learning React and i want to know if there's a method to grab an element from my array
and style it with css.
const card = [
{
id: 1,
title: "DEEP EARTH",
Background: "images/earth.jpg",
},
{
id: 2,
title: "NIGHT ARCADE",
Background: "images/arcade.jpg",
},
{
id: 3,
title: "SOCCER TEAM VR",
Background: "images/soccer.jpg",
}
]
As early was said you can use map and inline styles
return (<div>
{card.map((item) => (
<div key={item.id}>{item.title}
styles={{background:url(item.src)}}
>
</div>
))}
</div>);
You will need to render this using map, and then you could apply any css you want with it.
this might help:
https://reactjs.org/docs/lists-and-keys.html
you could add a property to the ones you want styled:
const card = [
{
id: 1,
title: "DEEP EARTH",
Background: "images/earth.jpg",
style: {"color": "red"}
},
{
id: 2,
title: "NIGHT ARCADE",
Background: "images/arcade.jpg",
},
];
and then use map:
{
card.map((cardItem, index) => {
return <div>
<span style={cardItem.style}>{cardItem.title}</span>
</div>
})
}
and in the same manner you could have that property point to a css class you have defined and use className.

Is it possible to pass variable to antD Statistics component in React?

I'm trying to render data from a variable / function into an antD Statistic Component. It appears it can only take a raw string or number, but I need to pass data from props into it.
Is there a way around this? See code below - I would like to pass scenarios[0].investment into "Statistic title="Investment" value={scenarios[0].investment}" but it doesn't allow it. Current code below works AS-IS but breaks when I replace it with scenarios[0].investment
class RenderSummary extends Component {
state = {
clicked: false,
hovered: false,
};
hide = () => {
this.setState({
clicked: false,
hovered: false,
});
};
handleHoverChange = visible => {
this.setState({
hovered: visible,
clicked: false,
});
};
handleClickChange = visible => {
this.setState({
clicked: visible,
hovered: false,
});
};
render() {
const scenarios = this.props.scenarios;
const data = [
{
title: 'Title 1', content: <Statistic title="Investment" value={0}/>,
},
{
title: 'Title 2', content: <Statistic title="T Savings" value={0}/>,
},
{
title: 'Title 2', content: <Statistic title="E Savings" value={0}/>,
},
];
const hoverContent = <div>This is hover content.</div>;
const clickContent = <div>This is click content.</div>;
const onClick = () => console.log("Works!");
return(
<div className="container">
<div className="site-card-wrapper">
<Row gutter={16}>
<Col span={12}>
<Card title="User Scenario 1" bordered={true}>
<List
grid={{ gutter: 16, column: 3 }}
dataSource={data}
renderItem={item => (
<List.Item>
{item.content}
</List.Item>
)}
/>
</Card>
</Col>
</Row>
</div>
</div>
);
}
}
scenarios in props as follows
"scenarios : [0: {id: 0, investment: 0, tSavings: 0, eSavings: 0 …},1: {id: 0, investment: 1, tSavings: 1, eSavings: 1 …}]"
I think the way you have structured your scenarios array or the way you are passing it is incorrect.
An example of how you could pass scenarios to RenderSummary:
const App = () => {
const scenarios = [
{investment: 1}, // Add your other properties
{investment: 2},
{investment: 3},
]
return <RenderSummary scenarios={scenarios}/>
}
If you pass scenarios like above, you can pass it in the way you wanted to:
const data = [
{
title: "Title 1",
content: <Statistic title="Investment" value={scenarios[0].investment} />,
},
{
title: "Title 2",
content: <Statistic title="T Savings" value={scenarios[1].investment} />,
},
{
title: "Title 2",
content: <Statistic title="E Savings" value={scenarios[2].investment} />,
},
];

How to arrow toggle up and down by clicked index? Vue

How do I rotate just that arrow icon based on the clicked item?
new Vue({
el: "#app",
data() {
return {
isToggled: false,
items: [{
id: 1,
name: "Test1"
},
{
id: 2,
name: "Test2"
},
{
id: 3,
name: "Test3"
},
{
id: 4,
name: "Test4"
},
]
}
},
methods: {
arrowToggle() {
this.isToggled = !this.isToggled;
},
getItems() {
return this.items;
}
},
mounted() {
this.getItems();
}
});
i {
border: solid black;
border-width: 0 3px 3px 0;
display: inline-block;
padding: 3px;
}
.down {
transform: rotate(45deg);
}
.up {
transform: rotate(-155deg);
}
.accordion {
display: flex;
background: lightblue;
align-items: center;
width: 100%;
width: 1000px;
justify-content: space-between;
height: 30px;
padding: 0 20px;
}
.arrow {
transform: rotate(-135deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" style="display: flex; justify-content: center; align-items: center;">
<div v-for="(item, index) in items" :key="index">
<div class="accordion" #click="arrowToggle()">
<p> {{ item.name }}</p>
<i :class="{ 'down': item.isToggled }" class="arrow"> </i>
</div>
</div>
</div>
Based on the clicked element do I want my arrow to rotate?
If i have 10 items and click on 2 items i want the icon to rotate there.
Failing to bind id to the clicked item and to bind that class to rotate the item
One thing is very important, I cannot set the isOpen parameter in my json ITEMS which is false which everyone recommends to me. I get it from a database and I don't have a condition for it.
You will have to toggle at individual item level. Note that I have used isToggled per item. Here is full code at: https://jsfiddle.net/kdj62myg/
Even if you get your items from DB, you can iterate through array and add a key named isToggled to each item.
HTML
<div id="app" style="display: flex; justify-content: center; align-items: center;">
<div v-for="(item, index) in items" :key="index">
<div class="accordion" #click="arrowToggle(item)">
<p> {{ item.name }}</p>
<i :class="{ 'down': item.isToggled, 'up': !item.isToggled }"> </i>
</div>
</div>
</div>
Vue
new Vue({
el: "#app",
data() {
return {
isToggled: false,
items: [{
id: 1,
name: "Test1",
isToggled: false
},
{
id: 2,
name: "Test2",
isToggled: false
},
{
id: 3,
name: "Test3",
isToggled: false
},
{
id: 4,
name: "Test4",
isToggled: false
},
]
}
},
methods: {
arrowToggle(item) {
return item.isToggled = !item.isToggled;
},
getItems() {
return this.items;
}
},
mounted() {
this.getItems();
}
});
You have to map your items and attach a custom data on it to solve your problem.
Items data should be like this
items: [{
id: 1,
name: "Test1",
isToggled: false
},
{
id: 2,
name: "Test2",
isToggled: false
},
{
id: 3,
name: "Test3",
isToggled: false
},
{
id: 4,
name: "Test4",
isToggled: false
},
]
and your toogle function should look like this.
arrowToggle(item) {
return item.isToggled = !item.isToggled;
},
Now, after you fetched the items from the server. You have to map it to attach a isToggled data on every item you have. like this.
getItems() {
axios.get('api/for/items')
.then(({data}) => {
this.items = data.map(item => ({
return {
name:item.name,
id:item.id,
isToggled:false
}
}))
});
}
The above arrowToggle function breaks vue reactivity (google vue reactivity for docs). According to the docs, changing an object property directly will break reactivity. To keep reactivity, the function should change to:
arrowToggle(item) {
this.$set(this.item, 'isToggled', item.isToggled = !item.isToggled)
return item.isToggled;
},

Categories