Search code examples
reactjsreact-hooksuse-effect

React/NextJS - useEffect - update specific array element


Hi I am trying to build a counter app for shooters to show percentage scoring during a match. I have created the following:

 import React, { useEffect, useState } from 'react';

import Counter from './components/counter';
import { NextPage } from 'next';

interface CountersProps {
  id: number;
  countHit: number;
  countMiss: number;
  percentage: string;
}

const Match: NextPage = () => {
  const [countersData, setCountersData] = useState<Array<CountersProps>>([]);
  const [id, setId] = useState(1);

  const generateId = () => {
    setId(id + 1);
    return id;
  };

  const handleHit = (id: number) => {
    const newCountersData = countersData.map((counter) => {
      if (counter.id !== id) return counter;
      else
        return {
          ...counter,
          countHit: counter.countHit + 1,
          setCountHit: counter.countHit,
          percentage: `${
            (counter.countHit / (counter.countHit + counter.countMiss)) * 100
          }%`,
        };
    });
    setCountersData(newCountersData);
  };

  const handleMiss = (id: number) => {
    const newCountersData = countersData.map((counter) => {
      if (counter.id !== id) return counter;
      else
        return {
          ...counter,
          countMiss: counter.countMiss + 1,
          percentage: `${
            (counter.countHit / (counter.countHit + counter.countMiss)) * 100
          }%`,
        };
    });
    setCountersData(newCountersData);
  };

  const addShooter = (id: number) => {
    setCountersData([
      ...countersData,
      { id: generateId(), countHit: 0, countMiss: 0, percentage: '0%' },
    ]);
  };

  useEffect(() => {
    console.log(countersData);
  }, [handleHit, handleMiss]);

  return (
    <main className="flex flex-col items-center justify-center flex-1 w-full px-20 text-center">
      <h1 className="text-6xl font-extrabold">
        The&nbsp;
        <a
          className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-700 to-emerald-700"
          href="https://nextjs.org"
        >
          Netball Counter
        </a>
      </h1>
      <div className="mt-10">
        <button
          type="button"
          className="inline-flex items-center px-4 py-2 text-base font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          onClick={() => addShooter(id)}
        >
          Add Shooter to Match
        </button>
      </div>
      <div className="mt-10">
        {countersData.map((counter) => (
          <Counter
            key={counter.id}
            {...counter}
            name="Mia"
            handleHit={handleHit}
            handleMiss={handleMiss}
          />
        ))}
      </div>
    </main>
  );
};

export default Match;

The component displays and the button to increment a hit or miss work however the calculation of the percentage is not working and needs to be done in the useEffect (before moving to an array it worked as only one player). I cant figure out how to update the specific array element associated with that player to update the percentage. Any help with this is much appreciated :-)

I have created the following: sandbox link


Solution

  • hit and miss values are not updated in handleMiss and handleHit.

    countHit should increase by +1 in handleHit:

         return {
           ...counter,
           countHit: counter.countHit + 1,
           setCountHit: counter.countHit,
           percentage: `${
                ((counter.countHit + 1) / (counter.countHit + 1 + counter.countMiss)) * 100
              }%`,
            };
    

    countMiss should increase by +1 in handleMiss:

    return {
            ...counter,
            countMiss: counter.countMiss + 1,
            percentage: `${
               (counter.countHit / (counter.countHit + counter.countMiss + 1)) * 100
              }%`,
            };
    

    sandbox link