Counter app doesn't work on React - javascript

I created 2 buttons and wanna them increase their value clicking on each button.
The code below doesn't work. Please, help ! All this code I render in html tag with class buttons.
class Buttons extends React.Component {
state = { score_1: 0, score_2: 0 };
updateScore_1() {
this.setState((prevState) => {
score_1: prevState.score_1 += 1
});
}
updateScore_2() {
this.setState((prevState) => {
score_2: prevState.score_2 += 1
});
}
render() {
return (
<div>
<button onClick={this.updateScore_1.bind(this)}>{this.state.score_1}</button>
<br />
<button onClick={this.updateScore_2.bind(this)}>{this.state.score_2}</button>
</div>
)
}
}
ReactDOM.render(
<Buttons />,
document.querySelector('.buttons')
);

You missed the keyword return in this.setState. Just add return in this.setState:
updateScore_2() {
this.setState((prevState) => {
return { score_2: prevState.score_2 + 1 }
});
}

You should write score_1: prevState.score_1 + 1 instead of score_1: prevState.score_1 += 1. In your case you are mutating the state, however using the setState callback you need to return the updated state
this.setState((prevState) => ({ // notice the `({` here
[key]: prevState[key] +1
}));
Also you can simply use one handler instead of two
class Buttons extends React.Component {
state = { score_1: 0, score_2: 0 };
updateScore(key) {
this.setState((prevState) => ({
[key]: prevState[key] +1
}));
}
render() {
return (
<div>
<button onClick={this.updateScore.bind(this, 'score_1')}>{this.state.score_1}</button>
<br />
<button onClick={this.updateScore.bind(this, 'score_2')}>{this.state.score_2}</button>
</div>
)
}
}
ReactDOM.render(
<Buttons />,
document.querySelector('.buttons')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div class="buttons"/>

Your issue I guess was from the fact that in the setState methods your were not returning the new state which should have been passed to the setState method
updateScore_1() {
this.setState((prevState) => {
score_1: prevState.score_1 + 1
});
}
A couple of parentheses solves your issue or you can just put a return in from of the new state object. See complete solution below:
class Buttons extends React.Component {
constructor(props) {
super(props);
this.state = { score_1: 0, score_2: 0 };
this.updateScore_1 = this.updateScore_1.bind(this);
this.updateScore_2 = this.updateScore_2.bind(this);
}
updateScore_1() {
this.setState(prevState => ({
score_1: (prevState.score_1 += 1)
}));
}
updateScore_2() {
this.setState(prevState => ({
score_2: (prevState.score_2 += 1)
}));
}
render() {
return (
<div>
<button onClick={this.updateScore_1}>{this.state.score_1}</button>
<br />
<button onClick={this.updateScore_2}>{this.state.score_2}</button>
</div>
);
}
}
ReactDOM.render(
<Buttons />,
document.querySelector('.buttons')
);

Related

Why can't I add any value to the array in state?

I have a lot of hits, which I want to add to an array once a hit is pressed. However, as far as I observed, the array looked like it got the name of the hit, which is the value. The value was gone in like half second.
I have tried the methods like building constructor, and doing things like
onClick={e => this.handleSelect(e)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
defaultValue={hit.name}
and so on
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
dropDownOpen:false,
text:"",
tags:[]
};
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
}
handleSelect = (e) => {
this.setState(
{ tags:[...this.state.tags, e.target.value]
});
}
render() {
const HitComponent = ({ hit }) => {
return (
<div className="infos">
<button
className="d-inline-flex p-2"
onClick={e => this.handleSelect(e)}
value={hit.name}
>
<Highlight attribute="name" hit={hit} />
</button>
</div>
);
}
const MyHits = connectHits(({ hits }) => {
const hs = hits.map(hit => <HitComponent key={hit.objectID} hit={hit}/>);
return <div id="hits">{hs}</div>;
})
return (
<InstantSearch
appId="JZR96HCCHL"
apiKey="b6fb26478563473aa77c0930824eb913"
indexName="tags"
>
<CustomSearchBox />
{result}
</InstantSearch>
)
}
}
Basically, what I want is to pass the name of the hit component to handleSelect method once the corresponding button is pressed.
You can simply pass the hit.name value into the arrow function.
Full working code example (simple paste into codesandbox.io):
import React from "react";
import ReactDOM from "react-dom";
const HitComponent = ({ hit, handleSelect }) => {
return <button onClick={() => handleSelect(hit)}>{hit.name}</button>;
};
class Tagsearch extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: []
};
}
handleSelect = value => {
this.setState(prevState => {
return { tags: [...prevState.tags, value] };
});
};
render() {
const hitList = this.props.hitList;
return hitList.map(hit => (
<HitComponent key={hit.id} hit={hit} handleSelect={this.handleSelect} />
));
}
}
function App() {
return (
<div className="App">
<Tagsearch
hitList={[
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
]}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
additionally:
note the use of prevState! This is a best practice when modifying state. You can google as to why!
you should define the HitComponent component outside of the render method. it doesn't need to be redefined each time the component is rendered!

How pass data from the child component (the child has its own state) to the parent?

Expected effect: click button -> call function setEditing() -> call function item() inside setEditing() -> this.state.isEditing changes to true -> in parent this.state.isEdit changes to true. When I call the item () function, the value of isEditing does not change
App
class App extends React.Component {
constructor() {
super();
this.state = {
isEdit = false;
};
}
handleSomething = (value) => {
this.setState(prevState => {
return {
isEdit: value
};
});
}
render() {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
handleSomething={this.handleSomething}
/>
)
}
</ul>
</div>
);
}
}
Todo
class Todo extends Component {
state = {
isEditing: false
}
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
})
this.item();
}
item = () => {
const { isEditing} = this.state;
this.props.handleSomething(isEditing);
}
render() {
return (
<button onClick={() => this.setEditing()}>Edit</button>
)
}
}
You'll need to call this.item after the state was changed, something like
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
}, this.item)
}
Also, if you want to derive a new state form the old one, you'll have to use something like this:
setEditing = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}), this.item)
}
Try basing your state change on the previous state, and call parent function in a callback :
setEditing = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}), this.item)
}
Because as written in the React doc :
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
(https://reactjs.org/docs/react-component.html#setstate)
class Todo extends React.Component {
state = {
isEditing: false
}
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
},this.item())
}
item = () => {
const { isEditing} = this.state;
this.props.handleSomething(isEditing);
}
render() {
return (
<button onClick={() => this.setEditing()}>
Edit
</button>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
isEdit : false,
todos : [
"test 1",
"test 2"
]
};
}
handleSomething = (value) => {
this.setState(prevState => {
return {
isEdit: value
};
});
}
render() {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
handleSomething={this.handleSomething}
/>
)
}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>

React: How to update object in array map

I'm trying to update the value of an array when a button is clicked. But I can't figure out how to do so using this.setState.
export default class App extends React.Component {
state = {
counters: [{ name: "item1", value: 0 }, { name: "item2", value: 5 }]
};
render() {
return this.state.counters.map((counter, i) => {
return (
<div>
{counter.name}, {counter.value}
<button onClick={/* increment counter.value here */}>+</button>
</div>
);
});
}
}
How do I increment counter.value when the button is clicked?
Update
Since this is the accepted answer now, let me give another optimal method after #Auskennfuchs' comment. Here we use Object.assign and index to update the current counter. With this method, we are avoiding to use unnecessary map over counters.
class App extends React.Component {
state = {
counters: [{ name: "item1", value: 0 }, { name: "item2", value: 5 }]
};
increment = e => {
const {
target: {
dataset: { i }
}
} = e;
const { counters } = this.state;
const newCounters = Object.assign(counters, {
...counters,
[i]: { ...counters[i], value: counters[i].value + 1 }
});
this.setState({ counters: newCounters });
};
render() {
return this.state.counters.map((counter, i) => {
return (
<div>
{counter.name}, {counter.value}
{/* We are using function reference here */}
<button data-i={i} onClick={this.increment}>
+
</button>
</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" />
As an alternative method, you can use counter name, map over the counters and increment the matched one.
class App extends React.Component {
state = {
counters: [{ name: "item1", value: 0 }, { name: "item2", value: 5 }]
};
increment = name => {
const newCounters = this.state.counters.map(counter => {
// Does not match, so return the counter without changing.
if (counter.name !== name) return counter;
// Else (means match) return a new counter but change only the value
return { ...counter, value: counter.value + 1};
});
this.setState({counters: newCounters});
};
render() {
return this.state.counters.map((counter, i) => {
return (
<div>
{counter.name}, {counter.value}
<button onClick={() => this.increment(counter.name)}>+</button>
</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" />
If you use the handler like above it will be recreated on every render. You can either extract it to a separate component or use datasets.
class App extends React.Component {
state = {
counters: [{ name: "item1", value: 0 }, { name: "item2", value: 5 }]
};
increment = e => {
const {target} = e;
const newCounters = this.state.counters.map(counter => {
if (counter.name !== target.dataset.name) return counter;
return { ...counter, value: counter.value + 1};
});
this.setState({counters: newCounters});
};
render() {
return this.state.counters.map((counter, i) => {
return (
<div>
{counter.name}, {counter.value}
{ /* We are using function reference here */ }
<button data-name={counter.name} onClick={this.increment}>+</button>
</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" />
Consider refactoring the actual counter into its own component. That simplifies the state management as it encapsulates the component responsibility. Suddenly you don't need to update a nested array of objects, but you update just a single state property:
class App extends React.Component {
state = {
counters: [{ name: "item1", value: 0 }, { name: "item2", value: 5 }]
};
render() {
return this.state.counters.map((counter, i) => {
return (
<Counter name={counter.name} count={counter.value} />
);
});
}
}
class Counter extends React.Component {
state = {
count: this.props.count
}
increment = () => {
this.setState({
count: this.state.count+1
})
}
render() {
return (
<div>
{this.props.name}, {this.state.count}
<button onClick={this.increment}>+</button>
</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 could use the i that you already have to map the counters to a new array, replacing the target item with an updated count:
() => this.setState(({ counters }) => ({ counters: counters.map((prev, i2) => i === i2 ? { ...counter, count: counter.count + 1 } : prev) }))
You can have an handler that will map counters and update the corresponding counters item of the button clicked. Here we take i from the parent scope, and compare it to find the right item to change.
<button onClick={() => {
this.setState(state => ({
counters: state.counters.map((item, j) => {
// is this the counter that I want to update?
if (j === i) {
return {
...item,
value: item.value + 1
}
}
return item
})
}))
}}>+</button>
You can create a click handler like this
handleClick = (index) => {
this.setState(state => {
const obj = state.counters[index]; // assign the object at the index to a variable
obj.value++; // increment the value in the object
state.counters.splice(index, 1); // remove the object from the array
return { counters: [...state.counters, obj] };
});
}
Call it like this... <button onClick={() => handleClick(i)}>
It's possible to make this shorter. Just wanted to explain how you could go about it
With Array.splice you can replace an entry inside of an array. This will return a new array with the replaced value:
const {counters}= this.state
return counters.map((counter, i) => (
<div key={i}>
{counter.name}, {counter.value}
<button onClick={() => this.setState({
counters: counters.splice(i, 1, {
...counter,
value: counter.value+1,
}
)})}>+</button>
</div>
));
Also it's best practise to give every Fragment inside of a loop it's own unique key. And you can get rid of the return, because there's only the return inside of the map function.

How can I disable a particular button after click, in a buttons array?

My question is, how can I disable a particular button in a Button array depends on a click?
Below there is a SearchField component which consists of multiple buttons in it, I want to disable just the button clicked, but all the buttons turn to disabled, how can I solve this?
state = {
redirect: false,
loading: false,
alreadyAddedFav: false,
disabled: false
}
onClickedHandler = (recipe_id, token) => {
if (!this.props.isAuthenticated) {
this.setState({ redirect: true })
}
else {
const favData = {
recipe_id: recipe_id,
userId: this.props.userId
}
if (this.props.favRecipes.length > 0) {
if (!this.props.favRecipes.some(item => item.recipe_id === recipe_id)) {
console.log("added in the loop!")
this.props.onFav(favData, token);
this.setState({ disabled: true });
} else {
this.setState({ alreadyAddedFav: true })
console.log("it is already in the fav list!")
console.log(recipe_id)
}
} else {
this.props.onFav(favData, token);
this.setState({ disabled: true });
}
}
}
render() {
return (
<SearchResult
disabled={this.state.disabled}
key={ig.recipe_id}
title={ig.title}
imgSrc={ig.image_url}
clicked={() => this.onClickedHandler(ig.recipe_id, this.props.token)}
>
</SearchResult>)}
Here is a simple example, maybe you can enhance this according to your situation.
class App extends React.Component {
state = {
disableds: [],
};
handleClick = i =>
this.setState( currentState => ( {
disableds: [ ...currentState.disableds, i ],
} ) );
render() {
return (
<div className="App">
<Buttons onClick={this.handleClick} disableds={this.state.disableds} />
</div>
);
}
}
const Buttons = ( props ) => {
const buttons = [ { text: "foo" }, { text: "bar" }, { text: "baz" } ];
return (
<div>
{buttons.map( ( button, i ) => (
<button
key={button.text}
disabled={props.disableds.includes( i )}
onClick={() => props.onClick( i )}
>
{button.text}
</button>
) )}
</div>
);
};
ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
What I don't like here is the way I added the onClick handler to the button element. In this way, this arrow function will be recreated in every render. Not so big deal but I prefer using function references. To avoid this we can extract each button element into its component and use a separate handler again.

Asynchronously concatenating clicked numbers in a cleared string

The goal of my small React experiment is "clear the initial value of this.state.numString (outputs an empty string), then concatenate the clicked numbers into this.state.numString". To make it execute asynchronously, I took advantage of this.setState's callback where the concatenation of number strings happen.
class App extends Component {
state = {
numString: '12'
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
this.setState({
numString: ''
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
The result was not what I expected; it only adds the current number I click into the empty string then change it into the one I click next, no concatenation of string numbers happens.
Here is one way of doing this. As I said in my comment you are resetting the string in every setState. So, you need some kind of condition to do that.
class App extends React.Component {
state = {
numString: '12',
resetted: false,
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if ( !this.state.resetted ) {
this.setState({
numString: '',
resetted: true,
}, () => {
this.setState( prevState => ({
numString: prevState.numString.concat(num)
}))
})
} else {
this.setState(prevState => ({
numString: prevState.numString.concat(num)
}))
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = { padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem' };
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can do something like below to clear the state immediately and concatenate the state with previous state value
this.setState({
numString: ''
}, () => {
this.setState( prevState => ({
numString: prevState.numString + num
}));
});
The code above in your question , in first setState you are setting variable to empty and in the second setState it is concatenating new value with empty string state. Thats why it is not working.
Try something like below:
class App extends Component {
state = {
numString: '12',
isFirstTime: true
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if(this.state.isFirstTime){
this.setState({
numString: '',
isFirstTime: false
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}else{
this.setState({
numString: this.state.numString.concat(num)
})
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data- num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}

Categories

Resources