response- bootstrap accordion defaultActiveKey не открывает активную клавишу по умолчанию - PullRequest
0 голосов
/ 20 апреля 2020

ОБНОВЛЕНИЕ: После некоторых экспериментов я решил, что проблема в том, что у меня есть некоторый код (см. Раздел useEffect() ниже), который обновляет аккордеон при изменении объекта menu. На. первый рендер defaultActiveKey работает, но при последующих повторных рендерингах он не работает.

Я использую элемент управления Accordion из реагировать - bootstrap и могу получить базовые данные c пример из документации, работающей, но после небольшой настройки кода, аккордеон по умолчанию больше не открывается ... по умолчанию. Вот, пожалуй, самая интересная часть кода:

  return (
    <>
      <MenuMobile menuItems={menuItems} open={open} setOpen={setOpen} />
      <Navbar bg="white" variant="light" expand="lg" fixed="left">
        <Navbar.Brand href="/home">
          <img src={logo} width="113" height="40" alt={siteTitle + " Logo"} />
        </Navbar.Brand>
        <NavbarToggler open={open} setOpen={setOpen} />

        <Accordion
          defaultActiveKey={menu.defaultActiveKey}
          className="sidebar-menu"
          data-active={menu.defaultActiveKey}
        >
          {menu.cards.map((card, index) => {
            return (
              <Card key={index}>
                <CustomToggle title={card.title} eventKey={card.eventKey} anchors={card.anchors} />
                <Accordion.Collapse eventKey={card.eventKey}>
                  <Card.Body>
                    {card.anchors.map((anchor) => (
                      <a href={`#${anchor.href}`} key={anchor.href}>
                        {anchor.text}
                      </a>
                    ))}
                  </Card.Body>
                </Accordion.Collapse>
              </Card>
            );
          })}
        </Accordion>
      </Navbar>
    </>
  );

Я вывел menu.defaultActiveKey в атрибуте данных только для того, чтобы убедиться, что он все делает правильно, и это так. Я подозреваю, что проблема связана с тем фактом, что я генерирую дочерние компоненты <Card> динамически, но я не уверен, что это за исправление?

Весь исходный код приведен ниже, если вам интересно :

import React, { useState, useContext, useEffect } from "react";
import PropTypes from "prop-types";

import Navbar from "react-bootstrap/Navbar";
import AccordionContext from "react-bootstrap/AccordionContext";
import Accordion from "react-bootstrap/Accordion";
import Card from "react-bootstrap/Card";
import { useAccordionToggle } from "react-bootstrap/AccordionToggle";
import classNames from "classnames";
import queryString from "query-string";

import MenuMobile from "./menuMobile";
import NavbarToggler from "./navbarToggler";
import EduMenus from "../utility/educationMenus";
import logo from "../images/logo-white.svg";

const CustomToggle = ({ title, eventKey, anchors, callback }) => {
  const currentEventKey = useContext(AccordionContext);
  const onClickToggle = useAccordionToggle(eventKey, () => {
    callback(eventKey);
  });
  const isOpen = currentEventKey === eventKey;

  return (
    <div className={classNames("card-header", { open: isOpen })} onClick={onClickToggle}>
      <h2>{title}</h2>
      {!!anchors.length && <i className={classNames("fa", { "fa-angle-down": !isOpen }, { "fa-angle-up": isOpen })} />}
    </div>
  );
};

CustomToggle.propTypes = {
  title: PropTypes.string.isRequired,
  eventKey: PropTypes.string.isRequired,
  anchors: PropTypes.array,
  callback: PropTypes.func,
};

CustomToggle.defaultProps = {
  anchors: [],
  callback: () => null,
};

const DocsNavbar = ({ siteTitle, location }) => {
  const [open, setOpen] = useState(false);
  const [menu, setMenu] = useState(EduMenus.default);
  const menuItems = [
    {
      href: "/education/overview",
      text: "Education",
    },
    {
      href: "/home",
      text: "Business",
    },
    {
      href: "/home",
      text: "Travel",
    },
    {
      href: "/home",
      text: "Healthcare",
    },
  ];

  useEffect(() => {
    if (!!location && location.search !== "") {
      const params = queryString.parse(location.search);
      if (params.menu) {
        if (Object.prototype.hasOwnProperty.call(EduMenus, params.menu)) {
          setMenu(EduMenus[params.menu]);
        } else {
          console.error(`Menu named '${params.menu}' does not exist`);
        }
      }
    }
  });

  return (
    <>
      <MenuMobile menuItems={menuItems} open={open} setOpen={setOpen} />
      <Navbar bg="white" variant="light" expand="lg" fixed="left">
        <Navbar.Brand href="/home">
          <img src={logo} width="113" height="40" alt={siteTitle + " Logo"} />
        </Navbar.Brand>
        <NavbarToggler open={open} setOpen={setOpen} />

        <Accordion
          defaultActiveKey={menu.defaultActiveKey}
          className="sidebar-menu"
          data-active={menu.defaultActiveKey}
        >
          {menu.cards.map((card, index) => {
            return (
              <Card key={index}>
                <CustomToggle title={card.title} eventKey={card.eventKey} anchors={card.anchors} />
                <Accordion.Collapse eventKey={card.eventKey}>
                  <Card.Body>
                    {card.anchors.map((anchor) => (
                      <a href={`#${anchor.href}`} key={anchor.href}>
                        {anchor.text}
                      </a>
                    ))}
                  </Card.Body>
                </Accordion.Collapse>
              </Card>
            );
          })}
        </Accordion>
      </Navbar>
    </>
  );
};

DocsNavbar.propTypes = {
  siteTitle: PropTypes.string,
  location: PropTypes.object,
};

DocsNavbar.defaultProps = {
  siteTitle: ``,
};

export default DocsNavbar;

1 Ответ

0 голосов
/ 20 апреля 2020

Хорошо, так что я нашел обходной путь. Не уверен, что это лучший способ, но он работает, поэтому я выложу его, но если у кого-то есть лучшее решение, я весь слух.

По сути, я удалил defaultActiveKey, потому что кажется, что это так работает только при начальном рендеринге и явно устанавливает активный аккордеон с помощью activeKey, поддерживает его в состоянии и устанавливает это состояние при каждом изменении меню.

import React, { useState, useContext, useEffect } from "react";
import PropTypes from "prop-types";

import Navbar from "react-bootstrap/Navbar";
import AccordionContext from "react-bootstrap/AccordionContext";
import Accordion from "react-bootstrap/Accordion";
import Card from "react-bootstrap/Card";
import { useAccordionToggle } from "react-bootstrap/AccordionToggle";
import classNames from "classnames";
import queryString from "query-string";

import MenuMobile from "./menuMobile";
import NavbarToggler from "./navbarToggler";
import EduMenus from "../utility/educationMenus";
import logo from "../images/logo-white.svg";

const CustomToggle = ({ title, eventKey, anchors, callback }) => {
  const currentEventKey = useContext(AccordionContext);
  const onClickToggle = useAccordionToggle(eventKey, () => {
    callback(eventKey);
  });
  const isOpen = currentEventKey === eventKey;

  return (
    <div className={classNames("card-header", { open: isOpen })} onClick={onClickToggle}>
      <h2>{title}</h2>
      {!!anchors.length && <i className={classNames("fa", { "fa-angle-down": !isOpen }, { "fa-angle-up": isOpen })} />}
    </div>
  );
};

CustomToggle.propTypes = {
  title: PropTypes.string.isRequired,
  eventKey: PropTypes.string.isRequired,
  anchors: PropTypes.array,
  callback: PropTypes.func,
};

CustomToggle.defaultProps = {
  anchors: [],
  callback: () => null,
};

const DocsNavbar = ({ siteTitle, location }) => {
  const [open, setOpen] = useState(false);
  const [menu, setMenu] = useState(EduMenus.default);
  const [active, setActive] = useState(EduMenus.default.defaultActiveKey);

  const menuItems = [
    {
      href: "/education/overview",
      text: "Education",
    },
    {
      href: "/home",
      text: "Business",
    },
    {
      href: "/home",
      text: "Travel",
    },
    {
      href: "/home",
      text: "Healthcare",
    },
  ];

  useEffect(() => {
    if (!!location && location.search !== "") {
      const params = queryString.parse(location.search);
      if (params.menu) {
        if (Object.prototype.hasOwnProperty.call(EduMenus, params.menu)) {
          setMenu(EduMenus[params.menu]);
          setActive(EduMenus[params.menu].defaultActiveKey);
        } else {
          console.error(`Menu named '${params.menu}' does not exist`);
        }
      }
    }
  });

  return (
    <>
      <MenuMobile menuItems={menuItems} open={open} setOpen={setOpen} />
      <Navbar bg="white" variant="light" expand="lg" fixed="left">
        <Navbar.Brand href="/home">
          <img src={logo} width="113" height="40" alt={siteTitle + " Logo"} />
        </Navbar.Brand>
        <NavbarToggler open={open} setOpen={setOpen} />

        <Accordion activeKey={active} className="sidebar-menu" onSelect={(e) => setActive(e)}>
          {menu.cards.map((card, index) => {
            return (
              <Card key={index}>
                <CustomToggle title={card.title} eventKey={card.eventKey} anchors={card.anchors} />
                <Accordion.Collapse eventKey={card.eventKey}>
                  <Card.Body>
                    {card.anchors.map((anchor) => (
                      <a href={`#${anchor.href}`} key={anchor.href}>
                        {anchor.text}
                      </a>
                    ))}
                  </Card.Body>
                </Accordion.Collapse>
              </Card>
            );
          })}
        </Accordion>
      </Navbar>
    </>
  );
};

DocsNavbar.propTypes = {
  siteTitle: PropTypes.string,
  location: PropTypes.object,
};

DocsNavbar.defaultProps = {
  siteTitle: ``,
};

export default DocsNavbar;
...