简体   繁体   English

在 React 中单击链接关闭 DropDown 的最佳做法是什么?

[英]What is the best practice to close a DropDown on a Link click in React?

I have the following code: Link to Sandbox我有以下代码: Link to Sandbox

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch, Link } from "react-router-dom";
import "./styles.css";

function DropDown({ close }) {
  return (
    <ul>
      <li>
        <Link to="/" onClick={close}>
          Page 1
        </Link>
      </li>
      <li>
        <Link to="/page2" onClick={close}>
          Page 2
        </Link>
      </li>
      <li>
        <p>Dont hide dropdown when clicking me!</p>
      </li>
    </ul>
  );
}

function Header() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(prev => !prev)}>Toggle DropDown</button>
      {isOpen && <DropDown close={() => setIsOpen(false)} />}
    </div>
  );
}

function Page1() {
  return <h1>Page 1</h1>;
}

function Page2() {
  return <h1>Page 2</h1>;
}

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Header />
        <Switch>
          <Route exact path="/" component={Page1} />
          <Route path="/page2" component={Page2} />
        </Switch>
      </BrowserRouter>
    </div>
  );
}

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

How can I implement this without always passing an close callback down to every link in my dropdown?如果不总是将close回调向下传递到下拉列表中的每个链接,我该如何实现呢?

I am trying to implement this with a hook which listens on mousedown and checks if it is inside the dropdown and a click on type A but the problem is that then it closes the dropdown before the react redirect to the route.我试图用一个钩子来实现这个,这个钩子监听mousedown并检查它是否在下拉列表中并点击类型 A 但问题是它在反应重定向到路由之前关闭下拉列表。

The click outside I already have covered with a useClickAway hook but I would kind of need a useClickInsideOnLink hook as well.我已经用 useClickAway 钩子覆盖了外面的点击,但我还需要一个 useClickInsideOnLink 钩子。

You could create an internal component inside your DropDown component and use the close function by default, also passing the other props via rest parameter .您可以在DropDown组件中创建一个内部组件,并默认使用close function ,还可以通过rest parameter传递其他道具。 Something like this:像这样的东西:

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch, Link } from "react-router-dom";
import "./styles.css";

function DropDown({ close }) {

  const MyLink = ({...rest}) => {
    return (
      <Link {...rest} onClick={close}>
        Page 1
      </Link>
    )
  }

  return (
    <ul>
      <li>
        <MyLink to="/">
          Page 1
        </MyLink>
      </li>
      <li>
        <MyLink to="/page2">
          Page 2
        </MyLink>
      </li>
      <li>
        <p>Dont hide dropdown when clicking me!</p>
      </li>
    </ul>
  );
}

function Header() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(prev => !prev)}>Toggle DropDown</button>
      {isOpen && <DropDown close={() => setIsOpen(false)} />}
    </div>
  );
}

function Page1() {
  return <h1>Page 1</h1>;
}

function Page2() {
  return <h1>Page 2</h1>;
}

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Header />
        <Switch>
          <Route exact path="/" component={Page1} />
          <Route path="/page2" component={Page2} />
        </Switch>
      </BrowserRouter>
    </div>
  );
}

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

I think you have (at least) two options:我认为您(至少)有两个选择:

  • move Header into the pages (due to new pages are rendered, the header is re-initialized).将 Header 移动到页面中(由于呈现新页面,header 被重新初始化)。 Forked your CodeSandbox here在此处分叉您的 CodeSandbox
  • let the DropDown handle it's state on it's own, and include the toggle button to the dropdown.让 DropDown 自己处理它的 state,并将切换按钮包含到下拉列表中。 See CodeSandbox here在此处查看 CodeSandbox

Another possible way (I don't recommend it as it adds unnecessary complexity to a simple problem) (added by the comments of the asker):另一种可能的方式(我不推荐它,因为它给一个简单的问题增加了不必要的复杂性)(由提问者的评论添加):

  • Actually, a component updates, when props are changed or the state changes.实际上,当 props 更改或 state 更改时,组件会更新。 To quote react docs: If you want to “reset” some state when a prop changes , consider either making a component fully controlled or fully uncontrolled with a key instead.引用 react 文档:如果您想在 prop 更改时“重置”某些 state ,请考虑使用密钥完全控制完全不受控制的组件。 Fully uncontrolled would be option 2 then.完全不受控制的将是选项 2。

    To trigger the change of props, you could connect the Header withRouter .要触发道具的变化,您可以将Header与Router 连接。 This way the Header component get's notified when the location (and other routing-props) change.这样,当位置(和其他路由道具)发生变化时,Header 组件就会得到通知。 Based on that, you could apply updates to the isOpen state via the lifecycleMethod componentDidUpdate and compare the previous location.pathname with the current one and set the isOpen back to false if the pathname changed.基于此,您可以通过生命周期方法组件DidUpdate 对 isOpen state 应用更新,并将先前的 location.pathname 与当前的比较,如果路径名更改,则将 isOpen 设置回 false。 Actually I do not recommend that.其实我不建议这样做。 But here you go: CodeSandbox但是在这里你 go: CodeSandbox

Probably it's too late but if it can't help someone: You can destrcuture pathname from useLocation() hook, and on every pathname change you can set isOpen to false in useEffect.可能为时已晚,但如果它不能帮助某人:您可以从 useLocation() 挂钩中解构路径名,并且在每次路径名更改时,您可以在 useEffect 中将 isOpen 设置为 false。

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch, Link, useLocation } from "react-router-dom";
import "./styles.css";

function DropDown({ close }) {

  const MyLink = ({...rest}) => {
    return (
      <Link {...rest}>
        Page 1
      </Link>
    )
  }

  return (
    <ul>
      <li>
        <MyLink to="/">
          Page 1
        </MyLink>
      </li>
      <li>
        <MyLink to="/page2">
          Page 2
        </MyLink>
      </li>
      <li>
        <p>Dont hide dropdown when clicking me!</p>
      </li>
    </ul>
  );
}

function Header() {
  const [isOpen, setIsOpen] = useState(false);
  
  const { pathname } = useLocation();
  useEffect(() =>{
    setIsOpen(false);
  }, [pathname])

  return (
    <div>
      <button onClick={() => setIsOpen(prev => !prev)}>Toggle DropDown</button>
      {isOpen && <DropDown close={() => setIsOpen(false)} />}
    </div>
  );
}

function Page1() {
  return <h1>Page 1</h1>;
}

function Page2() {
  return <h1>Page 2</h1>;
}

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Header />
        <Switch>
          <Route exact path="/" component={Page1} />
          <Route path="/page2" component={Page2} />
        </Switch>
      </BrowserRouter>
    </div>
  );
}

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

Try to subscribe on current location like this:尝试像这样订阅当前位置:

const location = useLocation();
useEffect(() => {
  closeHeader();
}, [location]);

This one is good I guess;3我猜这个不错;3

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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