Having a massive nightmare, trying to calculate the sum of an array (of numbers), in ReactJS/State.
The code I've posted below works, if I'm not using state.
Basically, via a form a user enters a number and then submits.
What the user types, is watched via handleChange.
And then on handlSubmit, the number/value is stored in this.state.donated and this.state.sum. These are arrays.
When I look at console, both states, store an array of numbers. And collect each entry into the array fine.
I want to find and print the total of these numbers. So I'm using:
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = this.state.sum;
function add(a, b) {
return a + b;
}
// // TOTAL VALUE OF NUMBERS IN THE ARRAY
const cal = numbers.reduce(add, 0);
console.log('CALC', cal);
However, the problem I'm having is, if I submit for example, number 20. The sum function, console logs 0 first. When I enter a second number, it then console logs the first number I entered...
What am I doing wrong? There's my full/relevant code:
class App extends React.Component {
constructor() {
super();
this.state = {
number: '',
donated: [],
sum: [],
total: 0
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
// we need to bind otherwise this is undefined
}
//HANDLE FUNCTIONS
handleChange(e) {
this.setState({ number: e.target.value }, () =>
console.log('NUMBER', this.state.number));
}
handleSubmit(e) {
e.preventDefault();
this.setState({donated: this.state.donated.concat(this.state.number).map(Number)}, () => console.log('DONATED', this.state.donated));
this.setState({sum: this.state.donated.concat(this.state.number).map(Number)}, () => console.log('SUM', this.state.sum));
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = this.state.sum;
function add(a, b) {
return a + b;
}
// TOTAL VALUE OF NUMBERS IN THE ARRAY
const cal = numbers.reduce(add, 0);
console.log('CALC', cal);
document.forms['id_form'].reset();
}
render() {
return (
<main>
<section className="section">
<h1 className="is-size-2">DONATE FOR A GOOD CAUSE</h1>
<ProgressBar donated={this.state.donated} sum={this.state.sum}/>
<Form donated={this.state.donated} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
</section>
</main>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
According to official documentation
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.
Use setState callBack to calculate sum to get updated values
this.setState({sum: this.state.donated.concat(this.state.number).map(Number)}, () => {
console.log('SUM', this.state.sum);
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = this.state.sum;
function add(a, b) {
return a + b;
}
// TOTAL VALUE OF NUMBERS IN THE ARRAY
const cal = numbers.reduce(add, 0);
console.log('CALC', cal);
document.forms['id_form'].reset();
});
or you can simply update a sum variable after every new value submission. like this
setState({sum: this.state.sum + this.state.number, number: 0});
Related
I have a textarea array with values that can be updated. The text values in the array are updated when text is entered into the textarea. The array can also be updated externally.
The problem is that Textarea doesn't want to update its values with setState() like regular text does.
export function GameActions({}) {
const [array, setArray] = useState<Type>([]);
const changeText = (id: number, text: any) => {
actions[id].text = text;
setActions(actions);
};
return {actions.map((action, index) => (<Textarea
defaultValue={action.text}
onChange={(e) =>
changeText(index, e.currentTarget.value)
}
/>))};
};
Provided actions is an array state property, it should be:
setActions((actions)=>{
return actions.map((act,i)=>{
if(i == id) {
act.text = text;
}
return act;
});
});
When a state property is updated using its previous value, the callback argument should be used.
Also, to update an element of a state array, map should be used, rather than the indexation operator [].
Please read this article, to learn how to update state arrays.
(React 16.8.6)
I have the problem that when a variable change, it automatically changes also the state even if I don't call any setState. In the handleInputChange when I assign the value, it automatically updates the states clientsData and editedClientsData . I would like to update only editedClientsData.
Here is the code:
1 - set the state (fired on a button click):
getClients(calls) {
axios.all(calls)
.then(responseArr => {
let cleanedDate = []
responseArr.map(el => {
cleanedDate.push(el.data.data)
})
this.setState({
clientsData: cleanedDate,
editedClientsData: cleanedDate,
loading: false
})
this.loadDataChart()
});
}
2 - load the inputs fields
render(){
return (...
this.state.editedClientsData.map(this.renderInput)
...)
}
renderInput = (client, i) => {
const { activeYear } = this.state
return (<tr key={client.id}>
<td>{client.name}</td>
<td><Input
type="number"
name={client.id}
id="exampleNumber"
placeholder="number placeholder"
value={client.chartData[activeYear].r}
onChange={this.handleInputChange}
/></td>
<td>{client.chartData[activeYear].x}</td>
<td>{client.chartData[activeYear].y}</td>
</tr>
)
}
handleInputChange(event) {
let inputs = this.state.editedClientsData.slice();
const { activeYear } = this.state
for (let i in inputs) {
if (inputs[i].id == event.target.name) {
inputs[i].chartData[activeYear]['r'] = parseFloat(event.target.value)
console.log(inputs)
// this.setState({ editedClientsData: inputs })
break;
}
}
}
I tried to assign a fixed number
I tried to save as const {clientsData} = this.state , updating editedClientsData and the this.setState({clientsData})
All of these tests failed. Hope in your helps, thank you!
In the getClients function, you are assigning the same array cleanedDate to both the props (clientsData and editedClientsData) of the state.
Both the properties are pointing to the same array. So edit will be reflected to both the properties. Now, assuming that the array contains objects, so copying the array using slice will also not work, because both the different arrays are containing the references to the same data. Consider the example below:
var a = [{prop: 1}];
var b = a.slice();
var c = a.slice();
console.log(c == b); // false
b[0].prop = 2;
console.log(b[0].prop); // 2
console.log(c[0].prop); // also 2, because slice does shallow copy.
In this case you need to deeply copy the data, so no reference is duplicated. You can use Lodash utility for this or create your own utility for the same.
In reactjs I'm trying to render a component from a Map object. More specifically, on the press of a button I create a new "FormRow" component, and I store it in a javascript Map object for convenience (because I'm gonna to use it later on). Whenever it happens, I want to render the new component just added, but I can't figure out how to get it from the Map.
I tried to solve my problem in different ways, using:
myMap.forEach(key => {return myMap.get(key)});
myMap.forEach(component=> {return component});
myMap.values()
myMap.entries()
Object.keys(myMap).map(...)
What I didn't try:
https://stackoverflow.com/a/50183395/9282731 (I don't understand it clearly...)
Here is my code siplified:
FormComposer.js:
constructor() {
super();
this.state = {
counter: 0,
myMap: new myMap()
};
this.handleCreateNewRow = this.handleCreateNewRow.bind(this);
}
/** It returns a new FormRow. */
handleCreateNewRow() {
let cloneState = this.state;
let newRow = (
<FormRow // <-- the component that I want to render.
key={this.state.counter}
rowNumber={this.state.counter}
/>
);
cloneState.myMap.set(cloneState.counter, newRow);
cloneState.counter++; // increment the counter for rows
this.setState(cloneState);
}
render() {
return (
<div className="container-fluid m-3">
<div className="col col-9 float-left">
<div className="row">
<div className="text-left">
<h1>Form Builder</h1>
</div>
</div>
{/* Here is the problem! It automaticly loads all the row created previously */}
{this.state.myMap.forEach(value => {
console.log(value); // check
// it print me what I want, but it doesn't render it...
return value;
})}
</div>
</div>
);
}
The console.log(value) returns:
{$$typeof: Symbol(react.element), type: ƒ, key: "0", ref: null, props: {…}, …}
which is the output that I expected, but I don't know why the render() method doesn't render it. If you change the Map object with an Array, this example works, and the render() method renders to the user what he expects.
I don't see any reason to use a Map when you're using sequential numbers as the key, just an array would make more sense.
But to get all the values from a Map you use its values method:
{this.state.myMap.values()}
Your forEach didn't work because forEach always returns undefined and doesn't do anything with the return value from its callback.
Instead of forEach(), which returns undefined use map() on [...myMap.values()] (which is the values of the Map spread into a new array):
{[...this.state.myMap.values()].map(value => {
return value;
})}
This will return the values of the entries in myMap.
Or, since you don't need to modify the values, the short version:
{this.state.myMap.values()}
Also, be aware, that let cloneState = this.state doesn't actually clone the state, but rather creates a reference to the same object. So you could rewrite your handleCreateNewRow() as:
handleCreateNewRow() {
const counter = this.state.counter;
let newRow = (
<FormRow
key={counter}
rowNumber={counter}
/>
);
this.state.myMap.set(cloneState.counter, newRow);
this.setState({counter: counter + 1, myMap});
}
I don't know too much about Maps, but, as another point of view, I would approach this more declarative, so, lets say in the
handleCreateNewRow()
I would do
this.setState({ counter: this.state.counter++, anotherMeta.. })
and then render
var rows = [];
for (let i = 0; i < counter; i++) {
rows.push(<FormRow key={i} />);
}
return rows;
I´m trying to make a React calculator. It´s mostly done, but I have one problem I don´t know how to correct: I can´t seem to limit the number of operators an user can enter (for instance, I want to limit "++++" to just "+" and also prevent two operators getting joined: +- must become -). I tried resetting the state everytime an user enters an operator, but no dice. I´m seriously lost here. I thought about a Regex, but it seems to be rather problematic (way too many contexts to try).
class Calculator extends Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleClick = this.handleClick.bind(this);
}
handleClick(evt) {
const id = evt.target.id;
const result = evt.target.value;
this.setState(prevState => ({
value: `${prevState.value}${result}`.replace(/^0+\B/, '')
}));
if (id === 'equals') {
this.setState({ value: math.eval(this.state.value) });
} else if (id === 'clear') {
this.setState({ value: 0 });
}
}
}
You could use regular expressions to solve this. The main problem with using the includes() based approach is that it does not enforce a correct format in the input string. Perhaps you could use a regular expression like this?
/^\d*([/\+-/*=]\d+)*$/gi
This would prevent problems like multiple operands like +++, and so on:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };
this.handleClick = this.handleClick.bind(this);
}
handleClick(evt) {
const result = evt.target.value;
// Update state
this.setState({
value: result
});
// Comine previous state with input value
//const combination = `${ this.state.value }${result}`;
console.log(result, 'combination', result)
// Use regular expression to check valid input. If invalid
// prevent further processing
if(!result.match(/^\d*([/\+-/*=]\d+)*$/gi)) {
console.error('Invalid input')
return
}
this.setState({ calculated: eval(result) });
}
render() {
return <h1>
<input value={this.state.value} onChange={(e) => this.handleClick(e)}/>
<p>{ this.state.calculated }</p>
</h1>
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
There is a functioning JSFiddle here for you to try out
You could save a list with operations and the crrently typed character. Then before inserting check if it's an operation to prevent adding multiple of them
example
const ops = ['/', '+', '-', '^']
this.setState({lastChar: result})
// Before setState with the full formula
if(this.state.lastChar === result && ops.includes(result)) else if (ops.includes(this.state.lastChat) && ops.includes(result)) return;
assuming result is only the typed/clicked character
problems
This strategy is definitely not perfect. For example, typing 1+-2 or 1*-3 wouldn't work. But from here on you could tweak it to fit your needs
I'm on my phone, sorry for the bad formatting
This question already has answers here:
Why calling setState method doesn't mutate the state immediately?
(3 answers)
Closed 5 years ago.
I have add and subtract functions in my react container, that are passed whenever spans '+' or '-' are clicked, in order to calculate the total 'price' of three 'products' which are given different price values.
My problem is: when I first click either button, it console logs out the initial value 0 first, and then with every click afterwards, adds or subtracts from the total but always one step behind.
So, for example, if I click + for 'apples, price: 2', my console logs 0. Then, I'll click 'pears, price: 5', and my console will log 2, adding on the value from the previous click, instead of adding on 5 to the total.
Am I doing something wrong with my add and subtract functions? I change state.total before console logging it out, so why this happening?
here is my code:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = {
total: 0
}
this.addFunction = this.addFunction.bind(this);
this.subtractFunction = this.subtractFunction.bind(this);
}
addFunction(product) {
var x = Number(product.price)
this.setState({total: this.state.total + x});
console.log('total is ' + this.state.total)
}
subtractFunction(product) {
var x = Number(product.price)
this.setState({total: this.state.total - x});
console.log('total is ' + this.state.total)
}
render() {
var createProducts = this.props.products.map((product, i) => {
return <Product
key={i}
title={product.title}
price={product.price}
addProductSetUp={() => this.addFunction(product)}
subtractProductSetUp={() => this.subtractFunction(product)}
/>
});
return (
<div>
<ul>
{createProducts}
</ul>
</div>
);
}
}
ReactJS's setState is an asynchronous operation and that's why you don't have it's up-to-date value right away.
Should you need access right after, you can use:
this.setState(prevState => ({
total: prevState.total + x,
}), () => {
console.log(this.state)
})
setState actions are asynchronous and are batched for performance gains. This is explained in documentation of setState:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
So in your example, the state has not yet been updated. You can use a callback to check on the state when it has been updated:
addFunction(product) {
var x = Number(product.price)
this.setState({total: this.state.total + x},
() => console.log('total is ' + this.state.total));
}
but that's not necessarily useful in your case. You can simply print out the computed value
addFunction(product) {
var x = Number(product.price);
var total = this.state.total + x;
this.setState({total});
console.log('total is ' + total);
}