Search code examples
javascriptreactjstwitter-bootstrapreact-bootstrapsemantic-ui-react

How to scroll and expand an Accordion by onClick on an icon from a component?


I have a component MainContentwith its child Component AddMock. There is a table in MainContent which shows some list of items. It has a certain action on each row like view and edit which are rendered by Icons by semantic-UI react. By clicking on each icon, I need to scroll up and expand an accordion. The accordion is in AddMock.

// AddMock.js
const AddMock = () => {
  return (
    <div className="row">
      <Accordion style={{ paddingLeft: "32px" }}>
        <Card className="collapseStyle">
          <Card.Header>
            <Accordion.Toggle as={Button} variant="link" eventKey="0">
              Add Mock
            </Accordion.Toggle>
          </Card.Header>
          <Accordion.Collapse eventKey="0">
            <Card.Body>
              <Container className="mockbody">
                <Header as="h3">Hierarchy</Header>
                <TabContent />
              </Container>
            </Card.Body>
          </Accordion.Collapse>
        </Card>
      </Accordion>
    </div>
  );
};

Below is the MainContent.js

const MainContent = () => {
  
  return (
    <div>
      <AddMock />
      <div className="container-fluid">
        <div className="row">
          <div className="col-md-12">
            <div className="card">
              <div className="card-header card-header-info">
                <h4 className="card-title ">MOCK</h4>
              </div>
              <div className="card-body">
                <form>
                  {loading ? (
                    <div className="table-responsive">
                      <table className="table">
                        <thead>
                          <tr>
                            <th>AppName</th>
                            <th>Parent_App</th>
                            <th>Created_Date</th>
                            <th>Req_Path</th>
                            <th>Resp_Code</th>
                            <th>Resp_Content_Type</th>
                            <th>Resp_Delay</th>
                            <th>Action</th>
                          </tr>
                        </thead>
                        <tbody>
                          {data.map((routes, index) => {
                            return routes.map(contents, index);
                          })}
                        </tbody>
                      </table>
                    </div>
                  ) : (
                    <Spinner
                      animation="border"
                      style={{ marginLeft: "620px" }}
                    />
                  )}
                </form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

Is it possible to implement this with window.scrollTo() or any other better way to implement this in react?


Solution

  • Using Ref

    To start things off, we can have a state that keeps the eventKey of the active Accordion

    const [activeKey, setActiveKey] = useState("");
    

    After that, we use a ref for our React-Bootstrap Accordion component. We need to use Forwarded Refs later on to at least assign a ref to the topmost DOM element of the Accordion component so we can scroll to it.

    As for the Icon, take a look at its onClick event, this simply sets the activeKey to "0" which is mapped with your Accordion.Collapse component's eventKey. This will serve as the trigger point for its "expanding".

    // we are going to need a ref to the Accordion element go get its position/use the scrollIntoView function
    const accordElem = useRef(null);
    
    const handleClickEdit = () => {
      setActiveKey("0"); // "0" here is as defined in your Accordion.Collapse eventKey
      accordElem.current.scrollIntoView({
        behavior: "smooth",
        block: "end",
        inline: "nearest"
      }); // initiate scroll to the "AddMock" Accordion component
    };
    
    return (
      <div>
        <AddMock
          activeKey={activeKey}
          setActiveKey={setActiveKey}
          ref={accordElem}
        />
    
        ...
    
        <Icon
          name="pencil"
          size="huge"
          style={{ cursor: "pointer" }}
          onClick={() => handleClickEdit("0")}
        />
    

    For the scrolling we can use https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView. In addition, there are of course other alternatives such as scrollTo

    Finally, here is how our Accordion component looks like with the forwarded ref & utilization of the activeKey prop taken from the parent state:

    const AddMock = React.forwardRef((props, ref) => {
      // optional re-toggling of expanded accordion
      function handleClickToggle(eventKey) {
        if (eventKey === props.activeKey) {
          props.setActiveKey("");
        } else {
          props.setActiveKey(eventKey);
        }
      }
    
      return (
        <div className="row" ref={ref}>
          <Accordion style={{ paddingLeft: "32px" }} activeKey={props.activeKey}>
            <Card className="collapseStyle">
              <Card.Header>
                <Accordion.Toggle
                  as={Button}
                  variant="link"
                  eventKey="0"
                  onClick={() => handleClickToggle("0")}
                >
                  Add Mock
                </Accordion.Toggle>
              </Card.Header>
              <Accordion.Collapse eventKey="0">
                <Card.Body>
                  ...
    

    CodeSandBox: https://codesandbox.io/s/react-semantic-ui-react-bootstrap-3ndyl?file=/src/App.js:2444-3230