简体   繁体   中英

Using Reactstrap: How to toggle only one Collapse at a time?

I am using Reactstrap to open and collapse multiple cards. Once opened, they stay open, and since I plan to use more of them (for articles), this will be a mess. Once I click on a button and open a card, I would like the others to close, so only one card is displayed at a time. How can I achieve this?

    const [isOpenInfo, setIsOpenInfo] = useState(false);
    const toggleInfo = () => setIsOpenInfo(!isOpenInfo);

    const [isOpenArticle1, setIsOpenArticle1] = useState(false);
    const toggleArticle1 = () => setIsOpenArticle1(!isOpenArticle1);

    const [isOpenArticle2, setIsOpenArticle2] = useState(false);
    const toggleArticle2 = () => setIsOpenArticle2(!isOpenArticle2);

In my menu, I have a button "More Info", when clicked, it opens a list of collapsed articles and when clicking on each title, it opens the article (but I just want one article to open at a time). So it's like a collapse inside a collapse...

<Button className="info-button" color="primary" onClick={toggleInfo}>
  More Info
</Button>

<Collapse isOpen={isOpenInfo}>
    <Card className="card">
        <CardBody className="card-body">

            <div className="section section-articles">
                <div className="articles-buttons">
                    <Button
                        className="article2-button"
                        color="primary"
                        onClick={toggleArticle2}
                    >
                      <h3>Article 2</h3>
                    </Button>
                    <Button
                        className="article1-button"
                        color="primary"
                        onClick={toggleArticle1}
                    >
                    <h3>Article 1</h3>
                    </Button>
               </div>

<Collapse isOpen={isOpenArticle2}>
    <Card className="card">
        <CardBody className="card-body">
            <Article2 />
        </CardBody>
    </Card>
</Collapse>
<Collapse isOpen={isOpenArticle1}>
    <Card className="card">
        <CardBody className="card-body">
            <Article1 />
        </CardBody>
    </Card>
</Collapse>


           </div>
        </CardBody>
    </Card>
</Collapse>

You can create a default variable consisting all your articles and use it to set the state. Create a single state variable for all of your collapsible.

const DEFAULT_ARTICLES = {
    article1: false,
    article2: false,
};

const [articles, setArticles] = useState(DEFAULT_ARTICLES);
const toggleArticle = (key) => setArticles({
    ...DEFAULT_ARTICLES,
    [key]: true,
});

And on your render function use the key to open the collapse and toggle the collapse.

<Collapse isOpen={isOpenInfo}>
                <Card className="card">
                    <CardBody className="card-body">
                        <div className="section section-articles">
                            <div class="articles-buttons">
                                <Button
                                    className="article2-button"
                                    color="primary"
                                    onClick={() => toggleArticle('article2')}
                                >
                                    <h3>Article 2</h3>
                                </Button>
                                <Button
                                    className="article1-button"
                                    color="primary"
                                    onClick={() => toggleArticle('article1')}
                                >
                                    <h3>Article 1</h3>
                                </Button>
                            </div>
                            <Collapse isOpen={articles['article1']}>
                                <Card className="card">
                                    <CardBody className="card-body">
                                        <Article2 />
                                    </CardBody>
                                </Card>
                            </Collapse>
                            <Collapse isOpen={articles['article2']}>
                                <Card className="card">
                                    <CardBody className="card-body">
                                        <Article1 />
                                    </CardBody>
                                </Card>
                            </Collapse>
                        </div>
                    </CardBody>
                </Card>
            </Collapse>

You can use one state to control all the collapses.

const [openedCollapse, setOpenedCollapse] = useState("");

const openCollapse = e => { // this is the button onClick handler
  setOpenedCollapse(e.target.dataset.collapse);
};

Then your jsx looks like this:

<Button
  className="article1-button"
  color="primary"
  data-collapse="article1" // A dataset param
  onClick={openCollapse}>
  <h3>Article 1</h3>
</Button>
<Collapse isOpen={openedCollapse === "article1"}>
    <Card className="card">
        <CardBody className="card-body">
            <Article2 />
        </CardBody>
    </Card>
</Collapse>

dataset info

You can use an object as state with a callback function when toggling the More Info collapse and then utilize a simple string to determine which article should be opened when the main Collapse is open and a Button was clicked inside of it.

For example, updating whether or not the main collapse is open:

const toggleMoreInfo = () => {
  setState(prevState => {
    // this gives us access to the current state when setState is executed
    // then we can inverse a boolean when the More Info button is clicked
    return {
     article: "", // resets the open article
     moreInfoOpen: !prevState.moreInfoOpen // false => true || true => false
    }
  })
}

For example, updating which article should be opened:

 const handleArticleOpen = (article) => {
    setState((prevState) => 
      return {
      // keep whatever is in state as is by spreading it out (in this case, "moreInfoOpen" stays unchanged)
      ...prevState, // 
      // and just override the article with a passed in string
      article
    }));
  };

When dealing with coupled state, I like to use objects over individual states, since it's easier to keep both sets of state in sync. For a demo and the full code, look below...


Working demo:

编辑 ReactStrap 折叠示例


Code

import * as React from "react";
import { Button, Card, CardBody, Collapse } from "reactstrap";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";

export default function App() {
  const [state, setState] = React.useState({
    articleOpen: "",
    moreInfoOpen: false
  });
  const { article, moreInfoOpen } = state;

  const toggleMoreInfo = () => {
    setState((prevState) => ({
      article: "",
      moreInfoOpen: !prevState.moreInfoOpen
    }));
  };

  const handleArticleOpen = (article) => {
    setState((prevState) => ({
      ...prevState,
      article
    }));
  };

  return (
    <div className="app">
      <Button className="info-button" color="primary" onClick={toggleMoreInfo}>
        More Info
      </Button>
      <Collapse isOpen={moreInfoOpen}>
        <Card className="card">
          <CardBody className="card-body">
            <div className="section section-articles">
              <div className="articles-buttons">
                <Button
                  className="article2-button"
                  color="primary"
                  onClick={() => handleArticleOpen("2")}
                >
                  <h3>Article 2</h3>
                </Button>
                <Button
                  className="article1-button"
                  color="primary"
                  onClick={() => handleArticleOpen("1")}
                >
                  <h3>Article 1</h3>
                </Button>
              </div>

              <Collapse isOpen={article === "2"}>
                <Card className="card">
                  <CardBody className="card-body">
                    <div>Article 2</div>
                  </CardBody>
                </Card>
              </Collapse>
              <Collapse isOpen={article === "1"}>
                <Card className="card">
                  <CardBody className="card-body">
                    <div>Article 1</div>
                  </CardBody>
                </Card>
              </Collapse>
            </div>
          </CardBody>
        </Card>
      </Collapse>
    </div>
  );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM