简体   繁体   中英

How to refactor an if else if with previous state when using useState Hook?

I have 2 details tag, each has a control to toggle it on/off. Code snippet here . Clicking Control A should toggle on/off page A, clicking Control B should toggle on/off page B.

I did it with an if else if plus 2 useState , this would not be feasible when there are multiple details . How can I refactor the code such that maybe the if else if can be avoided and it detects which Control I click in a cleverer way?

Page.js

const Page = ({ name, isOpen, setIsOpen }) => {
  return (
    <>
      <details
        open={isOpen}
        onToggle={(e) => {
          setIsOpen(e.target.open);
        }}
      >
        <summary>Page {name} title</summary>
        <div>Page {name} contents</div>
      </details>
    </>
  );
};

export default Page;

Control.js

const Control = ({ toggle }) => {
  return (
    <>
      <a onClick={() => toggle("A")} href="#/">
        Control A
      </a>
      <br />
      <a onClick={() => toggle("B")} href="#/">
        Control B
      </a>
    </>
  );
};

App.js

export default function App() {
  const [isOpenA, setIsOpenA] = useState(false);
  const [isOpenB, setIsOpenB] = useState(false);

  const toggle = (name) => {
    if (name === "A") {
      setIsOpenA((prevState) => !prevState);
    } else if (name === "B") {
      setIsOpenB((prevState) => !prevState);
    }
  };

  return (
    <div className="App">
      <Control toggle={toggle} />
      <Page name={"A"} isOpen={isOpenA} setIsOpen={setIsOpenA} />
      <Page name={"B"} isOpen={isOpenB} setIsOpen={setIsOpenB} />
    </div>
  );
}

You can use an array to represent open ones

const [openPages, setOpenPages] = useState([])

And to toggle filter the array

const toggle = (name) => {
    if(openPages.includes(name)){
        setOpenPages(openPages.filter(o=>o!=name))
    }else{
         setOpenPages(pages=>{ return [...pages,name]}
    }
}

I would personally use an object as a map for your toggles as in something like:

const [isOpen, _setIsOpen] = useState({});

const setIsOpen = (pageName,value) => _setIsOpen({
 ...isOpen,
 [pageName]: value
});

const toggle = (name) => setIsOpen(name, !isOpen[name]);

and then in the template part:

<Page name={"A"} isOpen={isOpen["A"]} setIsOpen={toggle("A")} />

In this way you can have as many toggles you want and use them in any way you want

I think this would be quite cleaner, also you should put the various page names in an array and iterate over them as in

const pageNames = ["A","B"];

{
 pageNames.map( name => 
             <Page name={name} isOpen={isOpen[name]} setIsOpen={toggle(name)} />)
}

At least that's how I would go about it

Adithya's answer worked for me.

For future reference, I put the full working code here. The onToggle attribute in Page.js is not needed. All required is passing correct true/false to open={isOpen} in Page.js .

App.js:

export default function App() {
  const [openPages, setOpenPages] = useState([]);

  const toggle = (name) => {
    if (openPages.includes(name)) {
      setOpenPages(openPages.filter((o) => o !== name));
    } else {
      setOpenPages((pages) => {
        return [...pages, name];
      });
    }
  };

  return (
    <div className="App">
      <Control toggle={toggle} />
      <Page name={"A"} isOpen={openPages.includes("A")} />
      <Page name={"B"} isOpen={openPages.includes("B")} />
      <Page name={"C"} isOpen={openPages.includes("C")} />
    </div>
  );
}

Page.js

const Page = ({ name, isOpen }) => {
  return (
    <>
      <details open={isOpen}>
        <summary>Page {name} title</summary>
        <div>Page {name} contents</div>
      </details>
    </>
  );
};

Control.js remains the same.

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