How to update parent state and child components props at once (at the same time)

為{幸葍}努か 提交于 2021-01-27 07:07:58

问题


I'm new to React and basically I'm trying to update a parent App.js components' state and its child components (Team.js and Player.js) props at once. Currently only the parent components' state is being updated. I will try to explain it better with a step by step.

Here I have a parent component App.js

export default function App() {
      const teams = [
        {
          Name: "Chicago Bulls",
          Players: ["Michael Jordan", "Dennis Rodman", "Scottie Pippen"],
          Championships: 6
        },
        {
          Name: "Golden State Warriors",
          Players: ["Stephen Curry", "Klay Thompson", "Draymond Green"],
          Championships: 5
        },
        {
          Name: "Los Angeles Lakers",
          Players: ["Kobe Bryant", "LeBron James", "Magic Johnson"],
          Championships: 17
        }
      ];
      const [selectedTeam, setSelectedTeam] = useState({});
    
      const players = [
        { Name: "LeBron James", MVPs: 4 },
        { Name: "Michael Jordan", MVPs: 5 },
        { Name: "Stephen Curry", MVPs: "2" }
      ];
      const [selectedPlayer, setSelectedPlayer] = useState({});
    
      const [modalContent, setModalContent] = useState(false);
      const clickedComponent = useRef(null);
      const [show, setShowModal] = useState(false);
    
      const showModal = () => {
        setShowModal(true);
      };
    
      const hideModal = () => {
        setShowModal(false);
      };
    
      const handleModalContent = (clicked) => {
        switch (clicked) {
          case "Team":
            clickedComponent.current = (
              <Team
                teams={teams}
                selectedTeam={selectedTeam}
                setSelectedTeam={setSelectedTeam}
              />
            );
            break;
          case "Player":
            clickedComponent.current = (
              <Player
                players={players}
                selectedPlayer={selectedPlayer}
                setSelectedPlayer={setSelectedPlayer}
              />
            );
            break;
          default:
            clickedComponent.current = null;
            break;
        }
      };
    
      return (
        <div className="App" style={{ justifyContent: "space-evenly" }}>
          <div
            style={{
              justifyContent: "center",
              width: "100%",
              display: "flex",
              flexWrap: "wrap",
              margin: "40px 0px 0px 0px"
            }}
          >
            <div
              className="table-cell"
              onClick={() => {
                handleModalContent("Team");
                setModalContent(true);
                showModal();
              }}
            >
              <div className="table-cell-text">Click to access Team component</div>
            </div>
            <div
              className="table-cell"
              onClick={() => {
                handleModalContent("Player");
                setModalContent(true);
                showModal();
              }}
            >
              <div className="table-cell-text">
                Click to access Player component
              </div>
            </div>
          </div>
          <h3 style={{ marginTop: "30px" }}>
            The last selected team was: {selectedTeam.Name}
            <br />
            The last selected player was: {selectedPlayer.Name}
          </h3>
          <Modal show={show} modalClosed={hideModal}>
            {(modalContent && clickedComponent.current) || null}
          </Modal>
        </div>
      );
    }

This component has two arrays of objects (teams and players) that is sent to Team and Player components, respectively, as props. Team also receives selectedTeam and setSelectedTeam as props. Player receives selectedPlayer and setSelectedPlayer. Both components have a Modal component and a select input. In the Team component, the user will select a team and them it will be displayed the selected teams' players, while in the Player component a player will be select and them it will be displayed the amount of MVP of the selected player.

Team.js

const Team = (props) => {
  return (
    <div style={{ position: "relative", margin: "0 auto", width: "10em" }}>
      <h3>Select a team</h3>
      <div className="input-group col">
        <select
          onChange={(e) => {
            if (e === "") props.setSelectedTeam({});
            else {
              let foundTeam = props.teams.find(
                (team) => team.Name === e.target.value
              );
              props.setSelectedTeam(foundTeam);
            }
          }}
        >
          <option value="">Select a team...</option>
          {props.teams.map((team) => (
            <option key={team.Name} value={team.Name}>
              {team.Name}
            </option>
          ))}
        </select>
      </div>
      {Object.keys(props.selectedTeam).length > 0 ? (
        <div>
          <h3>{props.selectedTeam.Name} players: </h3>
          <br />
          {props.selectedTeam.Players.map((player, index) => (
            <div key={index}>{player}</div>
          ))}
        </div>
      ) : null}
    </div>
  );
};

export default Team;

Player.js

const Player = (props) => {
  return (
    <div style={{ position: "relative", margin: "0 auto", width: "10em" }}>
      <h3>Select a player</h3>
      <div className="input-group col">
        <select
          onChange={(e) => {
            if (e === "") props.setSelectedPlayer({});
            else {
              let foundPlayer = props.players.find(
                (player) => player.Name === e.target.value
              );
              props.setSelectedPlayer(foundPlayer);
            }
          }}
        >
          <option value="">Select a player...</option>
          {props.players.map((player) => (
            <option key={player.Name} value={player.Name}>
              {player.Name}
            </option>
          ))}
        </select>
      </div>
      {Object.keys(props.selectedPlayer).length > 0 ? (
        <div>
          <h3>
            {props.selectedPlayer.Name} MVPs: {props.selectedPlayer.MVPs}
          </h3>
        </div>
      ) : null}
    </div>
  );
};

export default Player;

So my problem is, if I select an option in the child components, they don't receive the updated selected option (I mean selectedTeam for Team component and selectedPlayer for Player component) immediatelly but in the father component App I have them updated. So, if I want them to get updated, I need to select an option, close the modal and reopen them again.

For example, here I have App.js visual:

If I open Team.js and select a team, I have selectedTeam updated in App.js but not in Team.js:

So, if I close the modal and reopen Team component again, then I have props.selectedTeam updated. So I have the following:

I have the same problem with Player component, but in this case regarding props.selectedPlayer

How can I make it work properly, I mean, how can I have props.selectedTeam and props.selectedPlayer updated at once in App such as in Team and Player, respectively? Thank you!

CodeSandbox

https://codesandbox.io/s/young-sun-gs117?file=/src/Team.js:51-1127


回答1:


Here is what you need to do, I refactored your code and add some comments on that so you know what I did that. one thing to remember is that almost you don't want to store a component in a state.

export default function App() {
  const teams = [
    {
      Name: "Chicago Bulls",
      Players: ["Michael Jordan", "Dennis Rodman", "Scottie Pippen"],
      Championships: 6,
    },
    {
      Name: "Golden State Warriors",
      Players: ["Stephen Curry", "Klay Thompson", "Draymond Green"],
      Championships: 5,
    },
    {
      Name: "Los Angeles Lakers",
      Players: ["Kobe Bryant", "LeBron James", "Magic Johnson"],
      Championships: 17,
    },
  ];
  const players = [
    { Name: "LeBron James", MVPs: 4 },
    { Name: "Michael Jordan", MVPs: 5 },
    { Name: "Stephen Curry", MVPs: "2" },
  ];

  // This makes typo mistake less and will give you auto complete option
  const componentType = {
    team: "Team",
    player: "Player",
  };

  const [selectedTeam, setSelectedTeam] = useState({});
  const [selectedPlayer, setSelectedPlayer] = useState({});
  // the modalContent state and show state are doing the same thing so one of them is unneccessary
  const [show, setShowModal] = useState(false);
  const [clickedComponent, setClickedComponent] = useState("");

  const showModal = () => {
    setShowModal(true);
  };

  const hideModal = () => {
    setShowModal(false);
  };

  const handleModalContent = (clicked) => {
    setClickedComponent(clicked);
  };

  return (
    <div className="App" style={{ justifyContent: "space-evenly" }}>
      <div
        style={{
          justifyContent: "center",
          width: "100%",
          display: "flex",
          flexWrap: "wrap",
          margin: "40px 0px 0px 0px",
        }}
      >
        <div
          className="table-cell"
          onClick={() => {
            handleModalContent(componentType.team);
            showModal();
          }}
        >
          <div className="table-cell-text">Click to access Team component</div>
        </div>
        <div
          className="table-cell"
          onClick={() => {
            handleModalContent(componentType.player);
            showModal();
          }}
        >
          <div className="table-cell-text">
            Click to access Player component
          </div>
        </div>
      </div>
      <h3 style={{ marginTop: "30px" }}>
        The last selected team was: {selectedTeam.Name}
        <br />
        The last selected player was: {selectedPlayer.Name}
      </h3>
      <Modal show={show} modalClosed={hideModal}>
        {clickedComponent === componentType.player ? (
          <Player
            players={players}
            selectedPlayer={selectedPlayer}
            setSelectedPlayer={setSelectedPlayer}
          />
        ) : clickedComponent === componentType.team ? (
          <Team
            teams={teams}
            selectedTeam={selectedTeam}
            setSelectedTeam={setSelectedTeam}
          />
        ) : null}
      </Modal>
    </div>
  );
}



回答2:


The way I know how is to just use Hooks useState and useEffect and just update that state on select change.

Hope the below example for Player helps (worked in your code sandbox unless I am not answering your question):

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

const Player = (props) => {
  const [test, setTest] = useState("");

  useEffect(() => {
    console.log("props:", props);
    setTest(props.selectedPlayer);
  }, [props]);

  return (
    <div style={{ position: "relative", margin: "0 auto", width: "10em" }}>
      <h3>Select a player</h3>
      <div className="input-group col">
        <select
          value={props.selectedPlayer}
          onChange={(e) => {
            if (e === "") props.setSelectedPlayer({});
            else {
              let foundPlayer = props.players.find(
                (player) => player.Name === e.target.value
              );
              props.setSelectedPlayer(foundPlayer);
              setTest(foundPlayer);
            }
          }}
        >
          <option value="">Select a player...</option>
          {props.players.map((player) => (
            <option key={player.Name} value={player.Name}>
              {player.Name}
            </option>
          ))}
        </select>
      </div>

      <h3>{test.Name} MVPs: {test.MVPs}</h3>
      {/* {Object.keys(props.selectedPlayer).length > 0 ? (
        <div>
          <h3>
            {props.selectedPlayer.Name} MVPs: {props.selectedPlayer.MVPs}
          </h3>
        </div>
      ) : null} */}
    </div>
  );
};

export default Player;


来源:https://stackoverflow.com/questions/65675052/how-to-update-parent-state-and-child-components-props-at-once-at-the-same-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!