Compare states from child components in react

Viewed 49

I'm learning react. I built a page that let user vote for different person. I was able to make the voting work. But I'd like to highlight the person who gets the most votes.

This requires to compare states. I'm not quite sure how to do it. The hint that I got is from reading stackoverflow is to store the state in the parent component instead of in the the child. I can't figure out how to do it with my code, especially with useState hook.

import React, { useState } from "react";


const employeeDirectory = [
  {
    firstName: "Alice",
    lastName: "A",
    id: 1,
  },
  {
    firstName: "Bob",
    lastName: "B",
    id: 2,
  },
  {
    firstName: "Cathy",
    lastName: "C",
    id: 3,
  },
];

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Vote for me</h1>
        {employeeDirectory.map((i) => {
          const { firstName, lastName, id } = i;
          return (
            <Profile
              key={id}
              firstName={firstName}
              lastName={lastName}
              id={id}
            />
          );
        })}

      </header>
    </div>
  );
}

const Profile = (props) => {
  console.log(props);
  const { firstName, lastName, id } = props;
  const [count, setCount] = useState(0);

  return (
    <div
      style={{
        background: id === 1 ? "yellow" : "white",
      }}
    >
      <h2>
        Name: {firstName} {lastName}
      </h2>
      <p>
        {firstName} received {count} votes.
      </p>
      <button onClick={() => setCount(count + 1)}>Vote for me</button>
    </div>
  );
};
export default App;
2 Answers

You can add a state in App component which will track the total votes received for each employee.

function App() {
  const [votes, setVotes] = useState({});
  const updateVote = (id, count) => setVotes({...votes, [id]: count });
  return (
    <div className="App">
      <header className="App-header">
        <h1>Vote for me</h1>
        {employeeDirectory.map((i) => {
          const { firstName, lastName, id } = i;
          return (
            <Profile
              key={id}
              firstName={firstName}
              lastName={lastName}
              id={id}
              updateVote={updateVote}
            />
          );
        })}

      </header>
    </div>
  );
}

Then use the updateVote prop passed to Profile component to update votes received for an employee with the id.

const Profile = (props) => {
  console.log(props);
  const { firstName, lastName, id, updateVote } = props;
  const [count, setCount] = useState(0);

  return (
    <div
      style={{
        background: id === 1 ? "yellow" : "white",
      }}
    >
      <h2>
        Name: {firstName} {lastName}
      </h2>
      <p>
        {firstName} received {count} votes.
      </p>
      <button onClick={() =>  {
       const newCount = count + 1;
       setCount(newCount); 
       updateVote(id, newCount);
       }}>Vote for me</button>
    </div>
  );
};

So you will have employee ID and votes received for each in the parent component.

Another way to do this is to add the votes in the initial state array itself and do the calculation of the highest vote in the parent using it. It is then passed to the child component from which the classname for highlighting is applied conditionally.

App.jsx

import React, { useState } from "react";
import "./styles.css";


let employeeDirectory = [
  {
    firstName: "Alice",
    lastName: "A",
    id: 1,
    votes: 0
  },
  {
    firstName: "Bob",
    lastName: "B",
    id: 2,
    votes: 0
  },
  {
    firstName: "Cathy",
    lastName: "C",
    id: 3,
    votes: 0
  }
];

function App() {
  const [userData, setUserData] = useState(employeeDirectory);
  let highScore =
    userData && userData.reduce((a, b) => (a.votes > b.votes ? a : b)).votes;
  return (
    <div className="App">
      <header className="App-header">
        <h1>Vote for me</h1>
        {userData &&
          userData.map((item) => {
            return (
              <Profile
                id={item.id}
                key={item.id}
                userData={item}
                setUserData={setUserData}
                userArray={userData}
                isWinner={item.votes === highScore}
              />
            );
          })}
      </header>
    </div>
  );
}

const Profile = (props) => {
  const clickHandler = (user) => {
    props.setUserData((prevState) =>
      prevState.map((item) => {
        if (item.id === user.id)
          return {
            ...item,
            votes: item.votes + 1
          };
        return item;
      })
    );
  };

  return (
    <div className={props.userData.votes!==0 &&props.isWinner ? "winner" : ""}>
      <h2>
        Name: {props.userData.firstName} {props.userData.lastName}
      </h2>
      <p>
        {props.userData.firstName} received {props.userData.votes} votes.
      </p>
      <button
        onClick={() => {
          clickHandler(props.userData);
        }}
      >
        Vote for me
      </button>
    </div>
  );
};
export default App;

style.css

.winner {
  background: yellow;
}

Also please do not set state from the original state value as state changes are asynchronous. It is advised to do take the previous state value like this :

 props.setUserData((prevState) =>
      prevState.map((item) => {
        if (item.id === user.id)
          return {
            ...item,
            votes: item.votes + 1
          };
        return item;
      })
    );
Related