user16860065
user16860065

Reputation:

Execute child component action in the parent using react

I am having two components, App and a panel. On button clcik, I add panel to the screen and all the actions corresponding actions inside of the panel is handled in the Panel component ( Actions are expand, collapse and close). Can I somehow execute the same actions inside of the app component using useImperativeHandle hook using ref's. Also can I execute onClose method inside of the Panel component, here i am actually as a callback.

https://codesandbox.io/s/basic-demo-card-6ywop7?file=/src/Panel.jsx:0-985

Can someone help me here

App

import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import Panel from "./Panel";

import "./styles.css";

function App() {
  const [card, setCard] = useState({
    cardId: "",
    cardBody: null
  });
  const ref = useRef();

  const handleClick = (cardId, cardBody) => {
    setCard({ cardId, cardBody });
  };

  const { cardId, cardBody } = card;
  return (
    <>
      <div className="main">
        <button onClick={() => ref?.current?.expandBtn()}>Open from out</button>
        <button onClick={() => handleClick("Panel 1", <h1>h1</h1>)}>
          Add Panel 1
        </button>
        <button onClick={() => handleClick("Panel 2", <div>div</div>)}>
          Add Panel 2
        </button>
      </div>
      {cardBody && (
        <div className="cards-container">
          <Panel
            key={cardId}
            cardId={cardId}
            cardBody={cardBody}
            onClose={() =>
              setCard({
                cardId: "",
                cardBody: null
              })
            }
          />
        </div>
      )}
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Panel

import React, { useImperativeHandle, useState, useRef } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faSquareMinus,
  faRectangleXmark
} from "@fortawesome/free-solid-svg-icons";

export default function Panel(props) {
  const [isMinimized, setIsMinimized] = useState(false);
  const { cardId, cardBody, onClose, ref } = props;
  const expandRef = useRef();

  useImperativeHandle(ref, () => {
    return {
      expandBtn: () => expandRef.current.onMaximize()
    };
  });

  const onMaximize = () => {
    setIsMinimized(!isMinimized);
  };

  return (
    <>
      <div className={isMinimized ? "card-min" : "card"}>
        <div className="card-actions">
          <span onClick={onMaximize}>{cardId}</span>
          {!isMinimized && (
            <FontAwesomeIcon
              icon={faSquareMinus}
              onClick={() => {
                setIsMinimized(true);
              }}
            />
          )}
          <FontAwesomeIcon icon={faRectangleXmark} onClick={onClose} />
        </div>
        <div className="card-body">{cardBody}</div>
      </div>
    </>
  );
}

Upvotes: 2

Views: 1080

Answers (1)

arjun.dev
arjun.dev

Reputation: 116

Yes, you can the child's function from the Parent using useImperativeHandle. What your implementation is missing is forwardRef. passing ref as props won't work. What you have to do is forward the ref from child to parent.

const Panel = React.forwardRef(function (props, ref) {
const [isMinimized, setIsMinimized] = useState(false);
const { cardId, cardBody, onClose } = props;

const onMaximize = () => {
  setIsMinimized(!isMinimized);
};

useImperativeHandle(ref, () => {
  return {
    expandBtn: onMaximize
  };
});

return (
  <>
  <div className={isMinimized ? "card-min" : "card"}>
    <div className="card-actions">
      <span onClick={onMaximize}>{cardId}</span>
      {!isMinimized && (
        <FontAwesomeIcon
          icon={faSquareMinus}
          onClick={() => {
            setIsMinimized(true);
          }}
        />
      )}
      <FontAwesomeIcon icon={faRectangleXmark} onClick={onClose} />
    </div>
    <div className="card-body">{cardBody}</div>
  </div>
  </>
);
})

export default Panel;

Pass the ref from the App

function App() {
  const [card, setCard] = useState({
  cardId: "",
  cardBody: null
 });
const ref = useRef();

const handleClick = (cardId, cardBody) => {
  setCard({ cardId, cardBody });
};

const { cardId, cardBody } = card;
return (
  <>
   <div className="main">
    <button onClick={() => ref?.current?.expandBtn()}>Open from 
   out</button>
    <button onClick={() => handleClick("Panel 1", <h1>h1</h1>)}>
      Add Panel 1
    </button>
    <button onClick={() => handleClick("Panel 2", <div>div</div>)}>
      Add Panel 2
    </button>
  </div>
  {cardBody && (
    <div className="cards-container">
      <Panel
        ref={ref}
        key={cardId}
        cardId={cardId}
        cardBody={cardBody}
        onClose={() =>
          setCard({
            cardId: "",
            cardBody: null
          })
        }
      />
    </div>
  )}
  </>
 );
}

This might help https://blogsbyarjun.hashnode.dev/how-to-update-childs-state-from-parent-in-react-1

Upvotes: 2

Related Questions