Search code examples
javascriptreactjsmodal-dialogstatereact-props

How to update parent's component using state from child component in React?


I have 3 components.

In ListCard.js, I map cards array and based on the card the user click on, I call handleChangeCardData to update the modal's text.

My question is: How do I update/change the modal's text when my handleChangeCardData function is inside ListCard.js and my modal is on the same level. (Both are in Board.js)

Board.js

const [cardTitle, setCardTitle] = useState("");
return (
     {columns.map((column, index) => (
            <div className="column__container" key={index}>
              <div className="column__header">
                <div className="columnHeader__name">
                  <p>{column.name ? column.name : "..."}</p>
                </div>
                <div className="columnHeader__button">
                  <button
                    className="btn btn-sm --create-card-btn"
                    data-bs-toggle="modal"
                    data-bs-target="#modal-card"
                    onClick={() => setColumnId(column.id)}
                  >
                    New item
                  </button>
                </div>
              </div>
              <Droppable droppableId={column.id}>
                {(provided, snapshot) => (
                  <div
                    className="column"
                    ref={provided.innerRef}
                    {...provided.droppableProps}
                  >
                    <ListCard columnId={column.id} />
                    {provided.placeholder}
                  </div>
                )}
              </Droppable>
            </div>
          ))}
    <ViewCardModal cardTitle={cardTitle} />
)

LisCard.js

const handleChangeCardData = (cardTitle) => {
    setCardTitle(cardTitle);
  }

return (
{cards.map((card, index) => (
        <>
        <div key={index}>
          <Draggable draggableId={card.id} index={index}>
            {(provided, snapshot) => (
              <div
                ref={provided.innerRef}
                {...provided.draggableProps}
                {...provided.dragHandleProps}
              >
                <div
                  className="card --listcard-card"
                  onClick={() => handleChangeCardData(card.title)}
                  data-bs-toggle="modal"
                  data-bs-target="#modal-card-details"
                  style={{ border: `2px solid ${card.color}` }}
                >
                  <div className="card-body">
                    <p>{card.title}</p>
                  </div>
                </div>
              </div>
            )}
          </Draggable>
        </div>
        </>
      ))}
)

ViewCardModal.js

function ViewCardModal(props) {
    return (
        <div>{props.cardTitle}</div>
    )
}

Solution

  • In general, lift state up. In this case, it sounds like that means moving the state into Board and then passing that state to whatever child components need it (as a prop), and the state setter to whatever (other) child components need it.

    Here's a minimal example of lifting state up. I haven't tried to recreate the full complexity of your example, just to provide an example of Parent having state that ChildA uses and ChildB sets:

    const {useState} = React;
    
    const ChildA = React.memo(({counter}) => {
        console.log("ChildA rendered");
        return <div>Counter = {counter}</div>;
    });
    
    const ChildB = React.memo(({setCounter}) => {
        console.log("ChildB rendered");
        return <input
                type="button"
                value="Increment Counter"
                onClick={() => setCounter(c => c + 1)}
            />;
    });
    
    const Parent = () => {
        const [counter, setCounter] = useState(0);
        return (
            <div>
                <ChildA counter={counter} />
                <ChildB setCounter={setCounter} />
            </div>
        );
    };
    
    ReactDOM.render(<Parent />, document.getElementById("root"));
    <div id="root"></div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.development.js"></script>

    If there are several levels of hierarchy between where the state is being held and a descendant component that needs it, you might use context instead of props (although you might also look at component composition instead). See those links for details.