简体   繁体   中英

React: How to setState in child component from parent component?

I'm new to React and am attempting to set up a Bootstrap modal to show alert messages.

In my parent App.js file I have an error handler that sends a Modal.js component a prop that triggers the modal to show, eg:

On App.js:

function App() {

  const [modalShow, setModalShow] = useState(false);

  // Some other handlers

  const alertModalHandler = (modalMessage) => {
    console.log(modalMessage);
    setModalShow(true);
  }

  return (
   // Other components.
  <AlertModal modalOpen={modalShow}/>
  )
}

And on Modal.js:

import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import "bootstrap/dist/css/bootstrap.min.css";

const AlertModal = (props) => {

  const [isOpen, setIsOpen] = useState(false);

  if (props.modalOpen) {
    setIsOpen(true);
  }

  return (
    <Modal show={isOpen}>
      <Modal.Header closeButton>Hi</Modal.Header>
      <Modal.Body>asdfasdf</Modal.Body>
    </Modal>
  );
};

export default AlertModal;

However, this doesn't work. I get the error:

Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

If I change the Modal component to be a 'dumb' component and use the prop directly, eg:

 const AlertModal = (props) => {

  return (
    <Modal show={props.modalOpen}>
     <Modal.Header closeButton>Hi</Modal.Header>
     <Modal.Body>asdfasdf</Modal.Body>
     </Modal>
      );
    };

It does work, but I was wanting to change the show/hide state on the Modal.js component level as well, eg have something that handles modal close buttons in there.

I don't understand why is this breaking?

And does this mean I will have to handle the Modal close function at the parent App.js level?

Edit - full app.js contents

import React, { useState } from 'react';

import './App.css';
import 'bootstrap/dist/css/bootstrap.css';
import AddUserForm from './components/addUserForm';
import UserList from './components/userList';
import AlertModal from './components/modal';

function App() {

  const [users, setUsers] = useState([]);
  const [modalShow, setModalShow] = useState(false);


  const addPersonHandler = (nameValue, ageValue) => {
    console.log(nameValue, ageValue);
  
    setUsers(prevUsers => {
      const updatedUsers = [...prevUsers];
      updatedUsers.unshift({ name: nameValue, age: ageValue });
      return updatedUsers;
    });
  };

  const alertModalHandler = (modalMessage) => {
    console.log(modalMessage);
    setModalShow(true);
  }

  let content = (
    <p style={{ textAlign: 'center' }}>No users found. Maybe add one?</p>
  );

  if (users.length > 0) {
    content = (
      <UserList items={users} />
    );
  }

  return (
    <>
      <div className="container">
        <div className="row">
          <div className="col-md-6 offset-md-3">
            <AddUserForm onAddPerson={addPersonHandler} fireAlertModal={alertModalHandler}/>
          </div>
        </div>
        <div className="row">
          <div className="col-md-6 offset-md-3">
            {content}
          </div>
        </div>
      </div>
      <AlertModal modalOpen={modalShow}/>
    </>
  );
}

export default App;

In your modal.js

you should put

if (props.modalOpen) {
    setIsOpen(true);
  }

in a useEffect.

React.useEffect(() => {if (props.modalOpen) {
    setIsOpen(true);
  }}, [props.modalOpen])

You should never call setState just like that. If you do it will run on every render and trigger another render, because you changed the state. You should put the setModalShow together with the if clause in a useEffect . Eg:

useState(() => {
  if (modalOpen) {
      setIsOpen(true);
    }
}, [modalOpen])

Note that I also restructered modalOpen out of props . That way the useEffect will only run when modalOpen changes.

If you already send a state called modalShow to the AlertModal component there is no reason to use another state which does the same such as isOpen .

Whenever modalShow is changed, it causes a re-render of the AlertModal component since you changed it's state, then inside if the prop is true you set another state, causing another not needed re-render when you set isOpen . Then, on each re-render if props.showModal has not changed (and still is true) you trigger setIsOpen again and again.

If you want control over the modal open/close inside AlertModal I would do as follows:

<AlertModal modalOpen={modalShow} setModalOpen={setModalShow}/>

Pass the set function of the showModal state to the modal component, and there use it as you see fit. For example, in an onClick handler.

modal.js:

import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import "bootstrap/dist/css/bootstrap.min.css";

const AlertModal = (props) => {

  const onClickHandler = () => {
       props.setModalOpen(prevState => !prevState)
  }

  return (
    <Modal show={props.modalOpen}>
      <Modal.Header closeButton>Hi</Modal.Header>
      <Modal.Body>asdfasdf</Modal.Body>
    </Modal>
  );
};

export default AlertModal;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM