Reputation: 69
Started today learning react and i ran into an issue that might be more JS related than actually react.
import React, { useState } from "react";
import Counter from "./counter";
function Counters() {
const [counters, setCounters] = useState([
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
]);
const handleDelete = (counterID) => {
setCounters(counters.filter((counter) => counter.id !== counterID));
};
const handleIncrement = (counterToBeUpdated) => {
console.log(counters);
const counters = [...counters];
const index = counters.indexOf(counterToBeUpdated);
counters[index] = { ...counterToBeUpdated };
counters[index].value++;
setCounters({ counters });
};
return (
<div>
{counters.map((counter) => (
<Counter
key={counter.id}
counter={counter}
onDelete={handleDelete}
onIncrement={handleIncrement}
/>
))}
</div>
);
}
export default Counters;
When child component calls handleIncrement i get the Reference error of trying to access counters before it was initialized.
Upvotes: 1
Views: 92
Reputation: 159
I think the main issue here is how you call handleIncrement
.
It is called without arguments meaning that counterToBeUpdated
will be undefined.
If you change onIncrement={handleIncrement}
to onIncrement={() => handleIncrement(counter.id)}
, that's half the battle.
The in handleIncrement
you use counters.indexOf(counterToBeUpdated)
. Array.indexOf()
returns the array position of the value you pass as argument. Since you are looking for an object, and object equality is a large topic in javascript i recommend you better use Array.find()
here.
This is how you would find your object inside the array.
const counterToBeUpdated = counters.find((counter) => counter.id === counterToBeUpdated);
Since this method still leaves a lot to do (increment counter, create new array with counters, set this array as state), i'll show you a shortcut by using Array.map()
:
const handleIncrement = (counterToBeUpdated) => {
setCounters(counters.map((counter) => {
if(counter.id === counterToBeUpdated){
return {
...counter,
value: counter.value + 1
};
} else {
return counter;
}
});
};
Array.map()
returns a new Array. The function you pass will be executed for every value in the array and you decide how the appropriate value in the new array shall look like.
Upvotes: 0
Reputation: 23141
It's because you try to use a variable with the same name from the outer scope:
const counters = [...counters];
const counters = [1, 2, 3];
function update() {
const counters = [...counters];
}
update();
// => "Uncaught ReferenceError: Cannot access 'counters' before initialization"
You don't even need to use new variables in your increment handler, you can use a counters.map()
to iterate over the elements and update the value required. The returned array will be a new array, so you can pass it to your setCounters()
function. Check the working demo below:
function Counter({ counter, onDelete, onIncrement }) {
return (
<div>
<span style={{marginRight: '1rem'}}>{counter.value}</span>
<button onClick={() => onIncrement(counter.id)}>Inc</button>
<button onClick={() => onDelete(counter.id)}>Delete</button>
</div>
);
}
function Counters() {
const [counters, setCounters] = React.useState([
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
]);
const handleDelete = (id) => {
setCounters(counters.filter((counter) => counter.id !== id));
};
const handleIncrement = (id) => {
setCounters(
counters.map((counter) =>
counter.id === id ? { ...counter, value: counter.value + 1 } : counter
)
);
};
return (
<div>
{counters.map((counter) => (
<Counter
key={counter.id}
counter={counter}
onDelete={handleDelete}
onIncrement={handleIncrement}
/>
))}
</div>
);
}
ReactDOM.render(<Counters />, 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>
Upvotes: 3