Home > Enterprise >  React: How to setState in child component from parent component?
React: How to setState in child component from parent component?

Time:02-01

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;

CodePudding user response:

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. E.g.:

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.

CodePudding user response:

In your modal.js

you should put

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

in a useEffect.

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

CodePudding user response:

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;
  •  Tags:  
  • Related