Consider the following code :
import React, { Component } from 'react';
class Counter extends Component {
state = { value: 5 };
increment = () => {
this.setState(prevState => ({
value: prevState.value + 1
}));
};
decrement = () => {
this.setState(prevState => ({
value: prevState.value - 1
}));
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
)
}
}
How can I make it so that whenever I click the Decrement button, the value will not be less than 0. The value's minimum will always be zero and not -1, -2 ,-3 ,-4 ...
Just set a minimum value in your decrementing code:
decrement = () => {
this.setState(prevState => ({
value: Math.max(prevState.value - 1, 0)
}));
};
That's how number input works. To simplify the code you could try to use validity state (if your target browsers support it)
onChange(e) {
if (!e.target.validity.badInput) {
this.setState(Number(e.target.value))
}
}
Example
you can test it
const onPressDecrement = () => setCount((prevCount) => (Math.max(prevCount - 1,1)));
On Way Use Conditional (ternary) operator in decrement Function
decrement = () => {
this.setState(prevState => ({
value: prevState.value ? prevState.value -1 : 0
}));
};
Related
I have 3 counter buttons but I want a separate button that will onClick increment all the counters by 1. What is the best way to implement it and to have the state change all of the counters at once? I tried adding a countAll and combining all the counts but the syntax seemed off and I am not sure how to do it.
import React, { Component } from 'react';
import Button from './components/Button';
class App extends Component {
constructor(props) {
super(props);
this.state = { counter1: 0, counter2: 0, counter3: 0 };
}
incrementCount1() {
this.setState(prevState => ({ counter1: prevState.counter1 + 1 }));
}
incrementCount2() {
this.setState(prevState => ({ counter2: prevState.counter2 + 1 }));
}
incrementCount3() {
this.setState(prevState => ({ counter3: prevState.counter3 + 1 }));
}
decrementCount1() {
this.setState(prevState => ({ counter1: prevState.counter1 - 1 }));
}
decrementCount2() {
this.setState(prevState => ({ counter2: prevState.counter2 - 1 }));
}
decrementCount3() {
this.setState(prevState => ({ counter3: prevState.counter3 - 1 }));
}
render() {
let { counter1, counter2, counter3 } = this.state
return (
<div className="App">
<h2>Count: { counter1 }</h2>
<Button title = { "+" } task = { () => this.incrementCount1(counter1) } />
<Button title = { "-" } task = { () => this.decrementCount1(counter1) } />
<h2>Count: { counter2 }</h2>
<Button title = { "+" } task = { () => this.incrementCount2(counter2) } />
<Button title = { "-" } task = { () => this.decrementCount2(counter2) } />
<h2>Count: { counter3 }</h2>
<Button title = { "+" } task = { () => this.incrementCount3(counter3) } />
<Button title = { "-" } task = { () => this.decrementCount3(counter3) } />
</div>
);
}
}
export default App;
Sample using bracket notation and public class fields syntax
countOperation = (field, diff) => () => {
this.setState(prevState => ({ [field]: prevState[field] + diff }));
};
<button title={"+"} onClick={this.countOperation("counter1", 1)} />
<button title={"-"} onClick={this.countOperation("counter1", -1)} />
Addition
If you like, you can make one step further to package the set of buttons to a common HOC which can return id on certain callback.
In this way, you won't need to bind the index/key for each of your elements multiple times if there are multiple callbacks.
countOperation = diff => (e, id) => {
this.setState(prevState => ({ [id]: prevState[id] + diff }));
};
<CustomButton
id="counter1"
title={"+"}
onClick={this.countOperation(1)}
/>
class CustomButton extends React.Component {
render() {
const { id, title, onClick } = this.props;
return <button title={title} onClick={e => onClick(e, id)} />;
}
}
I really like #keikai's solution for code reduction/DRY-principal, but if you didn't want to change your existing state shape, AND if your existing state is only counters, then this would do the trick by operating over the state as an object.
Takes the state object, converts to array of entries, and reduces them back to an object that represents the next state with all counters incremented by the incrementBy amount.
incrementAll(incrementBy = 0) {
this.setState(prevState =>
Object.entries(prevState).reduce((counters, [counterKey, count]) => {
counters[counterKey] = count + incrementBy;
return counters;
}, {})
);
}
Usage
<Button title = { "+ all" } task = { () => this.incrementAll(1) } />
<Button title = { "- all" } task = { () => this.incrementAll(-1) } />
Hello I had an idea to make a hook to increase the font size and save preferences in localStorage
basically I have a state that goes from 1 to 4, and then when I click the button add I add +1 to the state until I reach number 4
and on the remove button I remove 1 from the state until 1
But I have doubts on how to save this to my location
basically if i don't use my useState just with getInitialValue It works normally.
like this gif, If I add the value manually it works:
but if I try to use my setFont I have problems (as it is saved in localStorage):
and i got this on localStorage :
code:
export default function App() {
const { fontSize, setSize } = useFontSize();
console.log(fontSize);
return (
<div className="App">
<button
onClick={() => {
setSize(fontSize + 1);
}}
>
add
</button>
<button
onClick={() => {
setSize(fontSize + 1);
}}
>
remove
</button>
</div>
);
}
hook:
export default function useFontSize(defaultSize = { size: 1 }) {
const [fontSize, _setSize] = useState(getInitialSize);
function getInitialSize() {
const savedSize = localStorage.getItem('_size_acessibility_font');
const parsedSize = JSON.parse(savedSize);
if (parsedSize) {
const { size } = parsedSize;
if (size >= 1 && size <= 4) {
return size;
}
} else {
return defaultSize.size;
}
}
useEffect(() => {
console.log(fontSize, 'on useEffect to set on localStorage');
localStorage.setItem(
'_size_acessibility_font',
JSON.stringify({ size: fontSize }),
);
}, [fontSize]);
return {
fontSize,
setSize: ({ setSize, ...size }) => {
console.log(size, 'on function set size');
if (size > 4) {
return _setSize(4);
}
if (size < 1) {
return _setSize(1);
}
return _setSize(size);
},
};
}
example:
https://codesandbox.io/s/focused-newton-x0mqd
I don't know if this is the best logic for this context, if someone can help me.
This seems a tad overengineered and upsets a few hooks idioms. For example, returning a named object pair for a hook is less typical than an array pair. The set function itself is complex and returns the result of the _setSize calls. Naming could be clearer if fontSize matched setSize by using setFontSize.
({ setSize, ...size }) is problematic since the caller is (correctly) providing an integer.
Here's a minimal, complete version that fixes these issues (local storage is mocked since Stack Snippets is sandboxed):
const localStorageMock = (() => {
const storage = {};
return {
getItem: k => storage[k],
setItem: (k, v) => {storage[k] = v.toString();}
};
})();
const {useState, useEffect} = React;
const useFontSize = (defaultSize=1) => {
const clamp = (n, lo=1, hi=4) => Math.min(hi, Math.max(n, lo));
const clean = n => isNaN(n) ? defaultSize : clamp(+n);
const storageName = "_size_acessibility_font";
const fromStorage = clean(localStorageMock.getItem(storageName));
const [fontSize, setFontSize] = useState(fromStorage);
useEffect(() => {
localStorageMock.setItem(storageName, fontSize);
}, [fontSize]);
return [fontSize, size => setFontSize(clean(size))];
};
const App = () => {
const [fontSize, setFontSize] = useFontSize();
return (
<div>
<div>Font size: {fontSize}</div>
<button onClick={() => setFontSize(fontSize + 1)}>
+
</button>
<button onClick={() => setFontSize(fontSize - 1)}>
-
</button>
</div>
);
};
ReactDOM.createRoot(document.querySelector("#app"))
.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
In useFontSize, you return
return {
fontSize,
setSize: ({ setSize, ...size }) => {
console.log(size, 'on function set size');
if (size > 4) {
return _setSize(4);
}
if (size < 1) {
return _setSize(1);
}
return _setSize(size);
},
};
However, in App, you call setSize with just a number setSize(fontSize + 1); when it is expecting an object.
If you change useFontSize to return
return {
fontSize,
setSize: (size) => {
console.log(size, 'on function set size');
if (size > 4) {
return _setSize(4);
}
if (size < 1) {
return _setSize(1);
}
return _setSize(size);
},
};
It should work.
Note, you will want to clear your current local storage, or add some error checking.
Also note, although it is just an example, both add and remove use fontSize + 1
This question already has an answer here:
Why doesn't my arrow function return a value?
(1 answer)
Closed 3 years ago.
Following is my code in which I am trying to increment the count using click button but it's not updating the value. Though I am not getting any error in console as well. Let me know what I am doing wrong here.
JS Code -
class App1 extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.setCount = this.setCount.bind(this)
}
setCount() {
this.setState((state) => {
count: state.count + 1
})
}
render() {
return (
<>
<hr />
<h3>test increment</h3>
<button onClick={this.setCount}>Click</button>
<p>{this.state.count}</p>
</>
)
}
}
ReactDOM.render(<App1 />, document.getElementById('root'))
Codepen - https://codepen.io/anon/pen/LaMOEp
You are not returning anything. You could use return in side callback.
setCount() {
this.setState((state) => {
return {count: state.count + 1}
}))
}
Or you can use avoid using of return wrapping you return value in () after =>
setCount() {
this.setState((state) => ({
count: state.count + 1
}))
}
this.setState((state) => {
count: state.count + 1
})
In the above code, the curly brackets are the body of the function, count: is a line label, and state.count + 1 is an expression that never gets used. If you want to use the concise arrow function syntax to return an object literal, then you need to wrap the object in parentheses:
this.setState((state) => ({
count: state.count + 1
}))
The problem is in setCount(), where you miss a pair of parenthesis! Here's the correct version:
setCount() {
this.setState((state) => ({
count: state.count + 1
}));
}
There are two parenthesis more! One right after the => and one at then of the this.setState() call.
this.setState(prevState => ({
score: prevState.score + 10,
rightAnswers: prevState.rightAnswers + 1,
currentQuestion: setTimeout(() => {
prevState.currentQuestion + 1
}, 2000)
}))
}
On button click I change the state. My goal is to have a delay in currentQuestion state change, during which I want to show certain status messages, yet I want to update the score right away without delays.
What's the proper way to do that?
PS: This variant doesn't work, it's for the overall representation of what I want to do.
Thanks.
You can do this multiple ways:
1) Make two calls to setState. React will batch any concurrent calls to setState into one batch update, so something like this is perfectly fine:
this.setState( prevState => ({
score: prevState.score + 10,
rightAnswers: prevState.rightAnswers + 1
}));
setTimeout( () => {
this.setState( prevState => ({
currentQuestion: prevState.currentQuestion + 1
}));
}, 2000);
2) You can use the setState callback to update state after your first call is finished:
this.setState(prevState => ({
score: prevState.score + 10,
rightAnswers: prevState.rightAnswers + 1
}), () => {
setTimeout( () => {
this.setState( prevState => ({
currentQuestion: prevState.currentQuestion + 1
}));
}, 2000);
});
First use setState to change score and question with some value like null so that you know its updating and then also set timeout after that.
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {
score: 1,
question: "A"
}
}
update() {
this.setState(prev => ({
score: prev.score + 1,
question: null
}));
this.change = setTimeout(() => {
this.setState({question: "B"})
}, 2000)
}
render() {
let {score, question} = this.state;
let style = {border: "1px solid black"}
return (
<div style={style} onClick={this.update.bind(this)}>
<div>{score}</div>
<div>{question ? question : "Loading..."}</div>
</div>
)
}
}
ReactDOM.render( < Example / > , document.querySelector("#app"))
<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="app"></div>
I understand that react doesn't update state immediately then can someone tell me how i can log this state synchronously as my state is not a boolean and this answer didn't help me setState doesn't update the state immediately. Also i don't understand why after clicking on prev button it increments the value first and then it decrements
class A extends Component {
constructor(props) {
super(props);
this.state = {
value: 1
}
}
handleNext() {
this.setState(prevState => ({
value: prevState.value + 1
}));
console.log(this.state.value);
}
handlePrev() {
if(this.state.value > 1) {
this.setState(prevState => ({
value: prevState.value - 1
}));
}
console.log(this.state.value);
}
render() {
<Button bsStyle="primary" id="prev" onClick={() => this.handlePrev()}>Prev</Button>
<Button bsStyle="primary" id="next" onClick={() => this.handleNext()}>Next</Button>
}
The second argument to setState is a callback that executes after the state has updated.
handleNext() {
this.setState({ value: this.state.value + 1 }, () => ({
console.log(this.state.value);
});
}
From the setState docs:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
setState takes a callback as its second argument.
handleNext() {
this.setState(prevState => ({
value: prevState.value + 1
}),() => console.log('updated', this.state.value));
}
You code is fine, try printing this.state.value in your render function.
Example:
class A extends Component {
constructor(props) {
super(props);
this.state = {
value: 1,
};
}
handleNext() {
this.setState(prevState => ({
value: prevState.value + 1,
}));
}
handlePrev() {
if (this.state.value > 1) {
this.setState(prevState => ({
value: prevState.value - 1,
}));
}
}
render() {
const { value } = this.state;
return (
<div>
<h2>{ value }</h2>
<button id="prev" onClick={() => this.handlePrev()}>Prev</button>
<button id="next" onClick={() => this.handleNext()}>Next</button>
</div>
);
}
}
It seems like your handlePrev is incrementing then decrementing because you're constantly printing the previous state. So when you decrement you are printing the result of the previous increment.
|---------------------|-------------------------|
| Current State | Your console.log |
|---------------------|-------------------------|
| 1 | | init
|---------------------|-------------------------|
| 2 | 1 | increment
|---------------------|-------------------------|
| 3 | 2 | increment
|---------------------|-------------------------|
| 2 | 3 | decrement
|---------------------|-------------------------|