filipe
filipe

Reputation: 69

Learning React - ReferenceError

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

Answers (2)

Malte Peters
Malte Peters

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

Zsolt Meszaros
Zsolt Meszaros

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

Related Questions