Передача ссылки на проблему с перехватом

#javascript #reactjs #react-hooks

#javascript #reactjs #реагирование-перехваты

Вопрос:

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

Я включил пример в codeSanbox: https://codesandbox.io/s/9o03qx3n3o

Я подтвердил, что это работает, когда не внутри перехвата, инструменты React dev показывают, что он передает один и тот же элемент в обоих случаях. Я пытался использовать forwardRef, та же проблема, я пытался использовать стандартное состояние для хранения ссылки, но безрезультатно;

 --- Hook ---

const usePopperMenu = btnRef => {
  const [open, setOpen] = useState(false);

  const handleClose = event => {
    if (btnRef.current.contains(event.target)) {
      return;
    }
    setOpen(false);
  };
  const elems = ({ children }) => (
    <Popper
      open={open}
      anchorEl={btnRef.current}
      placement="bottom"
      transition
      disablePortal
    >
      {({ TransitionProps, placement }) => (
        <Grow
          {...TransitionProps}
          id="menu-list-grow"
          style={{
            transformOrigin:
              placement === "bottom" ? "center top" : "center bottom"
          }}
        >
          <Paper>
            <ClickAwayListener onClickAway={e => handleClose(e)}>
              <MenuList>{children}</MenuList>
            </ClickAwayListener>
          </Paper>
        </Grow>
      )}
    </Popper>
  );
  return [elems, setOpen, open];
};

--- App/Component ---

function App() {
  const btnRef = useRef(null);
  const [Menu, setOpen, open] = usePopperMenu(btnRef);
  return (
    <div className="App">
      <div>
        <Button
          buttonRef={btnRef}
          onClick={() => setOpen(!open)}
          variant="outlined"
          color="secondary"
        >
          Click Me!
        </Button>
        <Menu>
          <MenuItem>
            <Typography color="textPrimary">Menu Item 1</Typography>
          </MenuItem>
          <MenuItem>
            <Typography color="textPrimary">Menu Item 2</Typography>
          </MenuItem>
        </Menu>
      </div>
    </div>
  );
}
 

Ответ №1:

Для меня это хорошо работает — проблема с вашими стилями css. Контейнер кнопок имеет полный размер, и display:grid поэтому выпадающий список неуместен

Попробуйте:

 .App {
  display: flex;
  justify-content: center;
}
 

https://codesandbox.io/s/oprzyv665?fontsize=14

Ответ №2:

Проблема в вашем коде заключается в том, что вы запускаете перехват и передаете ему ссылку, которая изначально не назначена элементу button. Он будет назначен только после первоначального рендеринга и сообщения о том, что вы не перерисовываете свое приложение для повторной инициализации вашего перехвата, чтобы оно получило правильную ссылку. Также вы пытаетесь вписаться в пользовательский перехват, где они вам не нужны. То, чего вы пытаетесь достичь, можно просто сделать с помощью компонентной модели.

Ваш код Popper будет выглядеть так

 import React, { useState } from "react";
import Grow from "@material-ui/core/Grow";
import Paper from "@material-ui/core/Paper";
import Popper from "@material-ui/core/Popper";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import MenuList from "@material-ui/core/MenuList";

const PopperMenu = ({ target, children, open, handleClose }) => (
  <Popper
    open={open}
    anchorEl={target.current}
    placement="bottom"
    transition
    disablePortal
  >
    {({ TransitionProps, placement }) => (
      <Grow
        {...TransitionProps}
        id="menu-list-grow"
        style={{
          transformOrigin:
            placement === "bottom" ? "center top" : "center bottom"
        }}
      >
        <Paper>
          <ClickAwayListener onClickAway={e => handleClose(e)}>
            <MenuList>{children}</MenuList>
          </ClickAwayListener>
        </Paper>
      </Grow>
    )}
  </Popper>
);

export default PopperMenu;
 

и вы можете использовать это как

 import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import MenuItem from "@material-ui/core/MenuItem";
import PopperMenu from "./hooks";
import "./styles.css";

function App() {
  const btnRef = useRef(null);
  const [open, setOpen] = useState(false);
  const handleClose = event => {
    if (btnRef.current.contains(event.target)) {
      return;
    }
    setOpen(false);
  };
  return (
    <div className="App">
      <div>
        <Button
          buttonRef={btnRef}
          onClick={() => setOpen(!open)}
          variant="outlined"
          color="secondary"
        >
          Click Me!
        </Button>
        <PopperMenu target={btnRef} handleClose={handleClose} open={open}>
          <MenuItem>
            <Typography color="textPrimary">Menu Item 1</Typography>
          </MenuItem>
          <MenuItem>
            <Typography color="textPrimary">Menu Item 2</Typography>
          </MenuItem>
        </PopperMenu>
      </div>
    </div>
  );
}

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

Рабочая демонстрация