如何在Material-UI菜单中使用自定义功能组件

时间:2019-05-25 17:50:46

标签: reactjs material-ui react-hooks

我正在使用Material-UI设计要构建的React应用。我试图在<Menu>组件中使用自己的自定义功能组件,但遇到一个奇怪的错误:

Warning: Function components cannot be given refs.
Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?

Check the render method of ForwardRef(Menu).
    in TextDivider (at Filter.js:32)

发生这种情况的JSX看起来像这样:(第32行是TextDivider组件)

<Menu>
    <TextDivider text="Filter Categories" />
    <MenuItem>...</MenuItem>
</Menu>

TextDivider在哪里:

const TextDivider = ({ text }) => {
    const [width, setWidth] = useState(null);
    const style = {
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center'
    };

    const hrStyle = {
        width: `calc(100% - ${width}px)`,
        marginRight: 0,
        marginLeft: 0
    };

    const spanStyle = {
        color: '#555',
        whiteSpace: 'nowrap'
    };

    const ref = createRef();
    useEffect(() => {
        if (ref.current) {
            const width = ref.current.getBoundingClientRect().width + 35;
            console.log('width', width);
            setWidth(width);
        }
    }, [ref.current]);

    return (
        <div style={style}>
            <span ref={ref} style={spanStyle}>{ text }</span>
            <Divider className="divider-w-text" style={ hrStyle }/>
        </div>
    );
};

奇怪的是,当在常规容器中自行设置此组件时,该组件呈现完美的效果,但似乎仅在<Menu>组件内部呈现时才会引发此错误。

此外,我发现如果将TextDivider像这样包装在div中,该错误会完全消失,这真的很奇怪:

<Menu>
    <div>
        <TextDivider text="Filter Categories" />
    </div>
    <MenuItem>...</MenuItem>
</Menu>

但是,这对我来说就像是胶带,我宁愿理解潜在的问题,即为什么我不能只在菜单中渲染此组件。我没有在我的功能组件中使用任何类型的Material-UI引用,也没有在我的<Divider>组件中的<TextDivider>组件上放置引用,所以我不明白为什么会抛出此错误错误。

任何对这种行为发生原因的见解将不胜感激。

我尝试使用useCallback钩子,createRef()useRef(),并阅读了在功能组件中使用钩子时可以找到的所有内容。该错误本身似乎具有误导性,因为即使React文档本身也使用此处的功能组件引用显示:https://reactjs.org/docs/hooks-reference.html#useref

1 个答案:

答案 0 :(得分:1)

Menu使用Menu的第一个子项作为Menu内部使用的Popover组件的“内容锚”。 “内容锚点”是Popover尝试与锚点元素(菜单外部的元素,它是放置菜单的参考点)对齐的菜单中的DOM元素。

为了利用第一个孩子作为内容锚点,Menu向其添加了一个引用(使用cloneElement)。为了避免收到您收到的错误(并使定位正常工作),功能组件需要将ref转发到它提供的组件之一(通常是最外面的组件-在您的情况下为div)。

当您将div用作Menu的直接子代时,您不会收到错误消息,因为div可以成功接收到引用。

所引用的SakoBu文档包含有关如何将引用转发到功能组件的详细信息。

结果应类似于以下内容(但我正在通过电话回答此问题,如果出现语法错误,请您谅解):

const TextDivider = React.forwardRef(({ text }, ref) => {
    const [width, setWidth] = useState(null);
    const style = {
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center'
    };

    const hrStyle = {
        width: `calc(100% - ${width}px)`,
        marginRight: 0,
        marginLeft: 0
    };

    const spanStyle = {
        color: '#555',
        whiteSpace: 'nowrap'
    };
    // Separate bug here.
    // This should be useRef instead of createRef.
    // I’ve also renamed this ref for clarity, and used "ref" for the forwarded ref.
    const spanRef = React.useRef();
    useEffect(() => {
        if (spanRef.current) {
            const width = spanRef.current.getBoundingClientRect().width + 35;
            console.log('width', width);
            setWidth(width);
        }
    }, [spanRef.current]);

    return (
        <div ref={ref} style={style}>
            <span ref={spanRef} style={spanStyle}>{ text }</span>
            <Divider className="divider-w-text" style={ hrStyle }/>
        </div>
    );
});

您可能还会发现以下答案很有用:

What's the difference between `useRef` and `createRef`?