Counter Variable not incrementing React - javascript

I have 6 cubes displayed on the page and there is a counter.
When you click on a cube, its background changes to red and the counter increases by one, but the counter does not want to increase. I always have it 1. But if you remove the setcub([...cubikinapole]) line, then the counter starts working as it should, but now the background does not change. What to do?
import "./styles.css";
import {useState} from 'react'
export default function App() {
let [cubikinapole, setcub] = useState([
{id:1,title:1, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/1.png", style: 'none'},
{id:2,title:2, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/2.png" , style: 'none'},
{id:3,title:3, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/3.png" , style: 'none'},
{id:4,title:4, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/4.png" , style: 'none'},
{id:5,title:5, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/5.png" , style: 'none'},
{id:6,title:6, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/6.png" , style: 'none'},
])
let [count,se] = useState(0)
function a(el) {
se(count++)
el.style = 'active'
setcub([...cubikinapole])
console.log(count)
}
return (
<div className="App">
{cubikinapole.length !== 0 ?
cubikinapole.map((el)=>
<div id='cub' key={el.id} className={el.style} onClick={()=>a(el)}>
<img src={el.img} />
</div>
)
: console.log() }
</div>
);
}
CSS
.active{
background-color: red;
}

you're never supposed to change the value of a useState by the value itself, only by the setter - what you're doing where you're writing se(count++) is giving the old value of the count to se and then directly changing count by count++,
so it looks like a race condition, you can fix it by just changing it to se(count+1) and your code will run as it should

There are a few issues. Note: I've changed the names of some variable for clarity.
You can't set your counter like that. Ideally you want to take the previous state, and then update and return that. So setCount(count++) becomes setCount(prev => prev += 1).
ids should be unique. If you want to uniquely identify an element you can use a data attribute, and then pick up that value in the handler.
With the id in hand in your handler you should find the object in state that where the object id matches the id of the element you clicked on, and then update that. To do that make a copy of the state first, update the object, and then set the state using the changed copy.
It's not really clear whether it was the background of the cube you wanted to change, or the image itself so I've just left your code as-is. For the actual image to change would be slightly more difficult.
const { useState } = React;
// For clarity I'm passing in the image
// config to the component, and then setting the
// cube state with it
function Example({ config }) {
// Initialise the states
const [ cubes, setCubes ] = useState(config);
const [ count, setCount ] = useState(0);
// When an element is clicked take the
// id from its data set. Make a copy of the state
// and then find the index of the object that has a
// matching id. Then update that object with the new
// style, and set both the cube and count states
function handleClick(e) {
const { id } = e.currentTarget.dataset;
const copy = [...cubes];
const index = copy.findIndex(cube => cube.id === Number(id));
if (index >= 0) copy[index].style = 'active';
setCubes(copy);
setCount(prev => prev += 1);
}
// I've added a count element here.
// `map` over state adding the data attribute
// to the div element.
return (
<div className="App">
<div className="counter">Count: {count}</div>
{config.map(obj => {
const { id, style, img } = obj;
return (
<div
key={id}
data-id={id}
className={style}
onClick={handleClick}
><img src={img} />
</div>
);
})}
</div>
);
}
const config=[{id:1,title:1,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/1.png",style:"none"},{id:2,title:2,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/2.png",style:"none"},{id:3,title:3,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/3.png",style:"none"},{id:4,title:4,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/4.png",style:"none"},{id:5,title:5,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/5.png",style:"none"},{id:6,title:6,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/6.png",style:"none"}];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
.active { background-color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

You can try this code. count++ increases its value after the setCount function is executed, so you should use count+1 to pass the increased value to the function. And you'd better use useEffect to monitor the count value.
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
let [cubikinapole, setCub] = useState([
{
id: 1,
title: 1,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/1.png",
style: "none"
},
{
id: 2,
title: 2,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/2.png",
style: "none"
},
{
id: 3,
title: 3,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/3.png",
style: "none"
},
{
id: 4,
title: 4,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/4.png",
style: "none"
},
{
id: 5,
title: 5,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/5.png",
style: "none"
},
{
id: 6,
title: 6,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/6.png",
style: "none"
}
]);
let [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
function a(el) {
setCount((prev) => prev + 1);
el.style = "active";
setCub([...cubikinapole]);
}
return (
<div className="App">
{cubikinapole.length !== 0
? cubikinapole.map((el) => (
<div key={el.id} className={el.style} onClick={(e) => a(el)}>
<img src={el.img} alt={el.title} />
</div>
))
: console.log()}
</div>
);
}
Live demo: https://codesandbox.io/s/amazing-haze-0zdb26?file=/src/App.js:0-1369

Related

Updating single item in react state hook

I have array useState hook which consists of items with className. I want to update className of a single item from that hook. How do I update single value of a useState hook.Of course I am going to put condition before updating .
here is the code -
display.map( word =>
( Words[leftPointer] === word.props.children ?
{...display, word:(<span key={uuid()} className="singleWord greenword">
{Words[leftPointer]}</span>)} :
display )
)
setDisplay(display)
here display contains array of span.
what I want is go change the className based on the condition example- after updating the item
className = "singleWord greenword" should be className="singleWord" of that item.
Map function will return a new array so you just have to store the array in a new variable and set the state with that new variable
const newDisplay = display.map( (word) =>{
if(Words[leftPointer] === word.props.children){
return {word:(<span key={uuid()} className="singleWord greenword">
{Words[leftPointer]}</span>)}
}else{
return word
}
})
setDisplay(newDisplay)
I would recommend against setting className in the state's domain as that is part of the UI's domain. Instead data changes can be used to signal responses in the UI rendering. This is what it means for a program to be data-driven.
Here is a minimal verifiable example. Run the program below and change some quantities to see the display state update. The className is automatically changed based on conditions applied to the application state.
function App() {
const [display, setDisplay] = React.useState([
{item: "walnut", quantity: 0 },
{item: "peanut", quantity: 3 },
{item: "donut", quantity: 6 }
])
function update(index) { return event =>
setDisplay([
...display.slice(0, index),
{ ...display[index], quantity: Number(event.target.value) },
...display.slice(index + 1)
])
}
return <div>
{display.map((d, index) =>
<div key={index}>
{d.item}:
<input
className={d.quantity > 0 ? "instock" : "outofstock" }
value={d.quantity}
onChange={update(index)}
/>
</div>
)}
<div>{display.every(d => d.quantity > 0) ? "✅" : "❌"}</div>
<pre>{JSON.stringify(display, null, 2)}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
input { outline: 0 }
.instock { border-color: limegreen; }
.outofstock { border-color: red; }
pre { padding: 0.5rem; background-color: #ffc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
could you just use a state variable that holds the text for className and update that state variable on the useState hook to whatever you want?

Javascript - How to loop through an array over and over again in React

Imagine I have an array of objects like this:
const items = [
{name: "item 0"},
{name: "item 1"},
{name: "item 2"}
]
And a component like this:
const [currentItemIndex, setCurrentItemIndex] = useState(0)
setInterval(function () {
if (currentItemIndex < 3) {
setCurrentItemIndex(currentItemIndex + 1);
} else {
clearInterval();
}
}, 5000);
return (
<div>
<p> {items[currentItemIndex].name} <p>
</div>
)
I want to update currentItemIndex every 5 seconds so the div shows next item, and if it reaches the end, it should start over: 0, 1, 2, 0, 1, 2, 0, 1, 2 ....
The problem is: it loops correctly the first time, but after reaching the end, it shows the first item for a fraction of a second and then goes to the last item: 0, 1, 2, 0 (really quick to) 2.
What I'm doing wrong?
I searched for this, but every article is talking about "how to prevent infinite loops", not "how to use them"!
You can do it with useEffect and with setTimeout like this:
const items = [{ name: "item 0" }, { name: "item 1" }, { name: "item 2" }];
const App = () => {
const [currentItemIndex, setCurrentItemIndex] = React.useState(0);
React.useEffect(() => {
const id = setTimeout(
() => setCurrentItemIndex((currentItemIndex + 1) % items.length),
1000
);
return () => {
clearInterval(id); // removes React warning when gets unmounted
};
}, [currentItemIndex]);
return (
<div>
<p> {items[currentItemIndex].name} </p>
</div>
);
};
// export default App;
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I see a few problems with this logic that you created.
First of all, clearInterval needs to be provided a variable that you created when creating the setInterval function (as you can see here), so this clearInterval() is doing nothing.
Besides that, you do not want to clear your timer when currentItemIndex reaches its threshold, but instead you want it to go back to zero.
In React, we usually use useEffect for timing events as you need to clear them on unmount so you don't keep them running after your component is unmounted. Something like this:
useEffect(() => {
const timer = setInterval(yourFunction);
return () => clearInterval(timer);
}, [])
You can use this approach if you want to. You create a function using which will set state after 5 seconds using timeout. Once the state is set you again call the same method.
For sake of react you call this method once using useEffect.
import { useEffect, useState } from "react";
const items = [{ name: "item 0" }, { name: "item 1" }, { name: "item 2" }];
export default function App() {
const [currentItemIndex, setCurrentItemIndex] = useState(0);
useEffect(() => {
incrementAfterDelay(1000, 1, 2);
}, []);
const incrementAfterDelay = (delay, step, lastStep) => {
setTimeout(
() =>
setCurrentItemIndex((value) => {
return (value < lastStep)? value + step : 0
}, incrementAfterDelay(delay, step, lastStep)),
delay
);
};
return (
<div>
<p> {items[currentItemIndex].name} </p>
</div>
);
}

How can I add new elements to a list?

I was trying to add a new element of array to the list with update of one property (id). I want to make it 1 more than length of array.
But I get some weird outputs, with add every new object. All elements are getting array.length +1 value.
I made several variations of this code with let, const or even operating directly on this.state.produktsToBuy, and every time I got the same output
handleAddToShop = (produktToBuy) => {
const id = this.state.produktsToBuy.length+1;
produktToBuy.id = id + 1;
const produktsToBuy = this.state.produktsToBuy;
produktsToBuy.push(produktToBuy);
this.setState({produktsToBuy});
};
I Should get the output as 1,2,3,4,5,6,7
But on the end I get 7,7,7,7,7,7
Make sure you're not mutating the state directly. In JS, objects are a reference type. When you assign this.state.produktsToBuy to const produktsToBuy and push something to produktsToBuy, you're actually pushing to the original this.state.produktsToBuy and you modify the state.
You can use the spread operator (...) to create a shallow copy of the state (produktsToBuy):
class App extends React.Component {
state = {
items: [
{ name: "test item 1", price: 4.99 },
{ name: "test item 2", price: 7.99 },
{ name: "test item 3", price: 19.99 }
],
produktsToBuy: []
};
handleAddToShop = (produktToBuy) => {
this.setState((prev) => ({
produktsToBuy: [
...prev.produktsToBuy,
{
...produktToBuy,
id: prev.produktsToBuy.length + 1
}
]
}));
};
render() {
return (
<div className="App">
<div style={{ display: "flex" }}>
{this.state.items.map((item) => (
<div
key={item.name}
style={{
border: "1px solid #ccc",
margin: "1rem",
padding: "1rem",
textAlign: "center"
}}
>
<h3>{item.name}</h3>
<p>${item.price}</p>
<button onClick={() => this.handleAddToShop(item)}>Add</button>
</div>
))}
</div>
<pre>{JSON.stringify(this.state.produktsToBuy, null, 2)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You should be maintaining all of the previous state if there's anything other than just the produktsToBuy. Also, you always need the functional form of setState if anything you're setting is dependent on the previous state(as is OFTEN the case). And, like Zsolt said, you never mutate the state directly in React. Here's my answer (very similar to #Zsolt Meszaros'). Note: .concat creates a new array, so we don't have to worry about mutating the original.
handleAddToShop = (produktToBuy) => {
this.setState((prevState) => {
const { produktsToBuy } = prevState;
return {
...prevState,
produktsToBuy: produktsToBuy.concat([
{
...produktToBuy,
id: produktsToBuy.length + 1,
},
]),
};
});
};

How to find specific items in an array in React/Framer?

I am pulling down results from an API, like so:
const [state, setState] = React.useState({
matches: undefined,
chosenBets: [{}]
});
const API = "https://api.myjson.com/bins/i461t"
const fetchData = async (endpoint, callback) => {
const response = await fetch(endpoint);
const json = await response.json();
setState({ matches: json });
};
And rendering JSX based off it using the map() function:
export function MatchCardGroup(props) {
return (
<div>
{props.matches.map((match, i) => {
return (
<MatchCard
key={i}
matchCardIndex={i}
team_home={match.teams[0]}
team_away={match.teams[1]}
league_name={match.sport_nice}
odd_home={match.sites[0].odds.h2h[0]}
odd_draw={match.sites[0].odds.h2h[1]}
odd_away={match.sites[0].odds.h2h[2]}
onClick={props.onClick}
timestamp={match.timestamp}
/>
);
})}
</div>
);
}
I then have a card which has odds on it, each odd with its own click event:
export function MatchCard(props) {
const [state, setState] = React.useState({
selection: {
id: undefined
}
});
const {
timestamp,
team_home,
team_away,
league_name,
odd_away,
odd_draw,
odd_home,
onClick,
matchCardIndex,
selection
} = props;
const odds = [
{
id: 0,
label: 1,
odd: odd_home || 1.6
},
{
id: 1,
label: "X",
odd: odd_draw || 1.9
},
{
id: 2,
label: 2,
odd: odd_away || 2.6
}
];
const handleOnClick = (odd, oddIndex) => {
// need to changhe the selection to prop
if (state.selection.id === oddIndex) {
setState({
selection: {
id: undefined
}
});
onClick({}, matchCardIndex);
} else {
setState({
selection: {
...odd,
team_home,
team_away
}
});
onClick({ ...odd, oddIndex, team_home, team_away, matchCardIndex });
}
};
React.useEffect(() => {}, [state, props]);
return (
<div style={{ width: "100%", height: 140, backgroundColor: colour.white }}>
<div>
<span
style={{
...type.smallBold,
color: colour.betpawaGreen
}}
>
{timestamp}
</span>
<h2 style={{ ...type.medium, ...typography }}>{team_home}</h2>
<h2 style={{ ...type.medium, ...typography }}>{team_away}</h2>
<span
style={{
...type.small,
color: colour.silver,
...typography
}}
>
{league_name}
</span>
</div>
<div style={{ display: "flex" }}>
{odds.map((odd, oddIndex) => {
return (
<OddButton
key={oddIndex}
oddBackgroundColor={getBackgroundColour(
state.selection.id,
oddIndex,
colour.lime,
colour.betpawaGreen
)}
labelBackgroundColor={getBackgroundColour(
state.selection.id,
oddIndex,
colour.lightLime,
colour.darkBetpawaGreen
)}
width={"calc(33.3% - 8px)"}
label={`${odd.label}`}
odd={`${odd.odd}`}
onClick={() => handleOnClick(odd, oddIndex)}
/>
);
})}
</div>
</div>
);
}
In my App Component I am logging the returned object from the click event:
const onClick = obj => {
// check if obj exists in state.chosenBets
// if it exists, remove from array
// if it does not exist, add it to the array
if (state.chosenBets.filter(value => value == obj).length > 0) {
console.log("5 found.");
} else {
console.log(state.chosenBets, "state.chosenBets");
}
};
And what I want to do is this:
When the user clicks an odd of any given match, add that odd to chosenBets
If the user deselects the odd, remove that odd from chosenBets
Only 1 odd from each of the 3 possible odds of any match can be selected at any time
Bonus points: the selected odd is selected based on the global state from App, instead of local state. This is so if I edit the array elsewhere, it should update in the UI.
Any help would be greatly appreciated, I'm lost here!
Link to Codesandbox
I've taken a short look at your project, and here are a few pointers to help you out:
Objects are only equal by reference.
This means that
{ id: 0, matchCardIndex: 8 } === { id: 0, matchCardIndex: 8 }
is false, even if you expect it to be true. To compare them, you need to compare every key in the object:
value.id === obj.id && value.matchCardIndex === obj.matchCardIndex
This also affects the filter call you have in the index.tsx, so you should change the comparison there to something similar to
state.chosenBets.filter(value => value.id === obj.id && value.matchCardIndex === obj.matchCardIndex)
State should only live in one place
As you already mentioned, it would be better to keep the state in your index.tsx if it also you needed there, and don't keep it locally in the components further down the tree. I'd suggest having the components only render the state, and have handlers to change the state.
Example
Here's a fork of your code sandbox I think implements it in a way that you described: https://codesandbox.io/s/gifted-star-wg629-so-pg5gx

Not rendering JSX from function in React

The function is getting the value of a button click as props. Data is mapped through to compare that button value to a key in the Data JSON called 'classes'. I am getting all the data correctly. All my console.logs are returning correct values. But for some reason, I cannot render anything.
I've tried to add two return statements. It is not even rendering the p tag with the word 'TEST'. Am I missing something? I have included a Code Sandbox: https://codesandbox.io/s/react-example-8xxih
When I click on the Math button, for example, I want to show the two teachers who teach Math as two bubbles below the buttons.
All the data is loading. Just having an issue with rendering it.
function ShowBubbles(props){
console.log('VALUE', props.target.value)
return (
<div id='bubbles-container'>
<p>TEST</p>
{Data.map((item,index) =>{
if(props.target.value == (Data[index].classes)){
return (
<Bubble key={index} nodeName={Data[index].name}>{Data[index].name}
</Bubble>
)
}
})}
</div>
)
}
Sandbox Link: https://codesandbox.io/embed/react-example-m1880
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
const circleStyle = {
width: 100,
height: 100,
borderRadius: 50,
fontSize: 30,
color: "blue"
};
const Data = [
{
classes: ["Math"],
name: "Mr.Rockow",
id: "135"
},
{
classes: ["English"],
name: "Mrs.Nicastro",
id: "358"
},
{
classes: ["Chemistry"],
name: "Mr.Bloomberg",
id: "405"
},
{
classes: ["Math"],
name: "Mr.Jennings",
id: "293"
}
];
const Bubble = item => {
let {name} = item.children.singleItem;
return (
<div style={circleStyle} onClick={()=>{console.log(name)}}>
<p>{item.children.singleItem.name}</p>
</div>
);
};
function ShowBubbles(props) {
var final = [];
Data.map((item, index) => {
if (props.target.value == Data[index].classes) {
final.push(Data[index])
}
})
return final;
}
function DisplayBubbles(singleItem) {
return <Bubble>{singleItem}</Bubble>
}
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
json: [],
classesArray: [],
displayBubble: true
};
this.showNode = this.showNode.bind(this);
}
componentDidMount() {
const newArray = [];
Data.map((item, index) => {
let classPlaceholder = Data[index].classes.toString();
if (newArray.indexOf(classPlaceholder) == -1) {
newArray.push(classPlaceholder);
}
// console.log('newArray', newArray)
});
this.setState({
json: Data,
classesArray: newArray
});
}
showNode(props) {
this.setState({
displayBubble: true
});
if (this.state.displayBubble === true) {
var output = ShowBubbles(props);
this.setState({output})
}
}
render() {
return (
<div>
{/* {this.state.displayBubble ? <ShowBubbles/> : ''} */}
<div id="sidebar-container">
<h1 className="sidebar-title">Classes At School</h1>
<h3>Classes To Search</h3>
{this.state.classesArray.map((item, index) => {
return (
<button
onClick={this.showNode}
className="btn-sidebar"
key={index}
value={this.state.classesArray[index]}
>
{this.state.classesArray[index]}
</button>
);
})}
</div>
{this.state.output && this.state.output.map(item=><DisplayBubbles singleItem={item}/>)}
</div>
);
}
}
ReactDOM.render(<Sidebar />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The issue here is ShowBubbles is not being rendered into the DOM, instead (according the sandbox), ShowBubbles (a React component) is being directly called in onClick button handlers. While you can technically do this, calling a component from a function will result in JSX, essentially, and you would need to manually insert this into the DOM.
Taking this approach is not very React-y, and there is usually a simpler way to approach this. One such approach would be to call the ShowBubbles directly from another React component, e.g. after your buttons using something like:
<ShowBubbles property1={prop1Value} <etc...> />
There are some other issues with the code (at least from the sandbox) that you will need to work out, but this will at least help get you moving in the right direction.

Categories

Resources