1

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.

filipe
  • 59
  • 5
  • 2
    You have a few errors: this line `setCounters({ counters });` is wrong, counters is an array but you're changing it to an object. As for the reference error, you assign a local variable (that shadows the outer state) in the handleIncrement function, but you try to log it before assigning it. – Jared Smith Dec 23 '20 at 12:04
  • 1
    Does this answer your question? [What is the temporal dead zone?](https://stackoverflow.com/questions/33198849/what-is-the-temporal-dead-zone) – Jared Smith Dec 23 '20 at 12:05
  • 1
    You've got counters everywhere: `const counters = [...counters];`. Do you want to spread the `counters` from the outer scope and assign it to the same variable name? – Zsolt Meszaros Dec 23 '20 at 12:05

2 Answers2

3

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>
Zsolt Meszaros
  • 8,506
  • 12
  • 20
  • 30
0

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.

Malte Peters
  • 66
  • 1
  • 8