【问题标题】:Single ref is not able to set focus, in array of ref, in react单个参考无法在参考数组中设置焦点,在反应中
【发布时间】:2019-12-19 13:38:20
【问题描述】:

上下文

  • 我们这里只使用键盘,没有鼠标点击。
  • 图像我们有 10 个表格行。
  • 每一行都有一个菜单按钮。
  • 将弹出一个 4 项下拉菜单的选项卡。 (焦点在菜单按钮上)
  • 按向下箭头,焦点应位于下拉菜单的第一项中(在我的代码中不起作用)

代码

import React, { useState, useEffect, useRef, createRef } from "react";

const MyDropdown = ({ items, rowKey, disabled }) => {
  const listRefs = useRef([]);
  const [open, setOpen] = useState(false);
  const [uniqueKey, setUniqueKey] = useState("");
  const [selectedIndex, setSelectedIndex] = useState(-1);

  // focus
  useEffect(() => {
    if (open && selectedIndex > -1) {
      // this not working?????????
      listRefs.current[selectedIndex].focus();
    }
  }, [open, selectedIndex]);

  const setActionMenuDefaultState = (
    event,
    isPreventDefault,
    open,
    rowKey,
    selectedIndex
  ) => {
    if (isPreventDefault) event.preventDefault();
    setOpen(open);
    setUniqueKey(rowKey);
    setSelectedIndex(selectedIndex);
  };

  return (
    <div id={"bla" + rowKey}>
      <MyMenuButton
        onKeyDown={event => {
          if ([ENTER_KEY, SPACE_KEY].includes(event.key)) {
            //test
            console.log("enter or space");
            setActionMenuDefaultState(event, true, !open, rowKey, -1);
          } else if ([ARROW_DOWN_KEY].includes(event.key)) {
            //test
            console.log("down key");
            event.preventDefault();

            // menu is open
            if (open) {
              // bound
              if (selectedIndex < items.length) {
                //test
                console.log("still set");
                setSelectedIndex(selectedIndex + 1);
              }
              // focus this row, 1st item
            }
          }
        }}
      />
      {!disabled && open && uniqueKey === rowKey ? (
        <MyDivWrapper>
          {items.map((item, index) => {
            return (
              <MyList
                ref={listRef => (listRefs.current[index] = listRef)}
                key={rowKey + index}
                onKeyDown={event => {
                  console.log("Able to focus and enter");
                }}
              >
                <p>Download</p>
              </MyList>
            );
          })}
        </MyDivWrapper>
      ) : null}
    </div>
  );
};

export default MyDropdown;

我的理解

  • 我看这个examplethis
  • 引用数组const listRefs = useRef([]);
  • 我的列表是li
  • ref={listRef =&gt; (listRefs.current[index] = listRef)}

【问题讨论】:

    标签: javascript reactjs react-hooks react-ref


    【解决方案1】:

    找到了解决办法。重点是

    • 创建一个对象并分配像obj[index] = createRef
    • 需要引入 the_row_index(父索引)以及项目自身
    • menuItemRefs.current[parentRowIndex + menuItemActiveIndex].focus(); 而非menuItemRefs[parentRowIndex + menuItemActiveIndex].current... 访问每个菜单项
    • 并在useEffect中使用它
    • li 中分配ref={element =&gt;(menuItemRefs.current[parentRowIndex + itemIndex] = element)}
    • 最重要的是,tabIndex="0" 需要在li
    import React, {useState, useEffect, useRef, createRef, useContext} from 'react';
    
    const AppContext = React.createContext({
      name: 'AppContext'
    });
    
    function createMenuItemRefs(items, rowInd) {
      // obj
      let menuItemRefs = {};
      // loop each
      for (let i = 0; i < Object.keys(items).length; i++) {
        // When assign createRef, no current
        menuItemRefs[rowInd + i] = createRef();
      }
      return menuItemRefs;
    }
    
    function Menu({buttonName, parentRowIndex}) {
      const [currRowInd, setCurrRowInd] = useState('');
      const [open, setOpen] = useState(false);
      // press down key, will get 1st item which at index 0
      const [menuItemActiveIndex, setMenuItemActiveIndex] = useState(-1);
    
      const menuItems = {download: 'download', view: 'view', delete: 'delete'};
      const menuItemRefs = useRef(createMenuItemRefs(menuItems, parentRowIndex));
    
      // focus
      useEffect(() => {
        if (open && menuItemActiveIndex >= 0 && parentRowIndex !== '') {
          console.log('focus!!');
          menuItemRefs.current[parentRowIndex + menuItemActiveIndex].focus();
        }
      }, [menuItemActiveIndex, open, parentRowIndex]);
    
      const clickOutside = event => {
        if (event.target.nodeName !== 'HTML') {
          setOpen(false);
          return;
        }
        setOpen(false);
      };
    
      // close
      useEffect(() => {
        if (open) {
          document.addEventListener('click', clickOutside);
        } else {
          document.removeEventListener('click', clickOutside);
        }
    
        return () => {
          document.removeEventListener('click', clickOutside);
        };
      }, [open]);
    
      const buttonIconOnClick = (event, parentRowIndex) => {
        // close it
        setOpen(!open);
        // which one to close
        setCurrRowInd(parentRowIndex);
      };
    
      // on the button level
      const buttonIconKeyDown = (event, parentRowIndex) => {
        if (event.keyCode === 13) {
          // Enter pressed
          console.log('enter is pressed');
    
          setOpen(!open);
          setCurrRowInd(parentRowIndex);
        } else if (event.keyCode === 9) {
          // we cannot tab away
        } else if (event.keyCode === 40) {
          //test
          console.log('down arrow');
    
          // 38 is up arrow
    
          // No scrolling
          event.preventDefault();
    
          // set to 1st item in 0 index
          setMenuItemActiveIndex(0);
        }
      };
    
      const itemOnKeyDown = (event, itemIndex) => {
        if (event.keyCode === 13) {
          console.log('enter press fire redux event', itemIndex);
        } else if (event.keyCode === 40) {
          if (itemIndex < Object.keys(menuItems).length - 1) {
            console.log('down');
            setMenuItemActiveIndex(itemIndex + 1);
          }
        } else if (event.keyCode === 38) {
          if (itemIndex > 0) {
            console.log('up');
            setMenuItemActiveIndex(itemIndex - 1);
          }
        }
      };
    
      const itemOnClick = (event, itemIndex) => {
        // it is mouse click
        if (event.pageX !== 0 && event.pageY !== 0) {
          console.log('item click fire redux event', itemIndex);
        }
      };
    
      return (
        <div>
          <button
            onClick={event => {
              // it is mouse click
              if (event.pageX !== 0 && event.pageY !== 0) {
                //test
                console.log('parent buttonicon onclick: ');
                buttonIconOnClick(event, parentRowIndex);
              }
            }}
            onKeyDown={event => {
              //test
              console.log('parent buttonicon onkeydown: ');
              buttonIconKeyDown(event, parentRowIndex);
            }}
          >
            {buttonName}
          </button>
    
          {open && parentRowIndex === currRowInd && (
            <ul style={{padding: '5px', margin: '10px', border: '1px solid #ccc'}}>
              {Object.keys(menuItems).map((item, itemIndex) => {
                if (itemIndex === menuItemActiveIndex)
                  return (
                    <li
                      tabIndex="0"
                      key={itemIndex}
                      style={{
                        listStyle: 'none',
                        padding: '5px',
                        backgroundColor: 'blue'
                      }}
                      // put a ref
                      ref={element =>
                        (menuItemRefs.current[parentRowIndex + itemIndex] = element)
                      }
                      onClick={event => {
                        itemOnClick(event, itemIndex);
                      }}
                      // we want own index
                      onKeyDown={event => itemOnKeyDown(event, itemIndex)}
                    >
                      {item}
                    </li>
                  );
                else
                  return (
                    <li
                      tabIndex="0"
                      key={itemIndex}
                      style={{listStyle: 'none', padding: '5px'}}
                      ref={element =>
                        (menuItemRefs.current[parentRowIndex + itemIndex] = element)
                      }
                      onClick={event => {
                        itemOnClick(event, itemIndex);
                      }}
                      // we want own index
                      onKeyDown={event => itemOnKeyDown(event, itemIndex)}
                    >
                      {item}
                    </li>
                  );
              })}
            </ul>
          )}
        </div>
      );
    }
    
    function TableElement() {
      const items = [
        {
          file: 'file1',
          button: 'button1'
        },
        {
          file: 'file2',
          button: 'button2'
        },
        {
          file: 'file3',
          button: 'button3'
        }
      ];
      return (
        <table style={{borderCollapse: 'collapse', border: '1px solid black'}}>
          <tbody>
            {items.map((item, index) => {
              return (
                <tr key={index}>
                  <td style={{border: '1px solid black'}}>
                    <a href="#">{item.file}</a>
                  </td>
                  <td style={{border: '1px solid black'}}>
                    <Menu buttonName={item.button} parentRowIndex={index} />
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      );
    }
    
    function App() {
      const appContextObj = {};
    
      return (
        <>
          <AppContext.Provider value={appContextObj}>
            <TableElement />
          </AppContext.Provider>
        </>
      );
    }
    
    export default App;
    

    【讨论】:

      猜你喜欢
      • 2018-01-01
      • 2021-08-19
      • 1970-01-01
      • 2014-11-26
      • 1970-01-01
      • 2020-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多