Element: [Bug Report] El-submenu causes 'Maximum call stack exceeded' error in collapsed menu if it's the top element of a component

Created on 5 Nov 2019  ·  9Comments  ·  Source: ElemeFE/element

Element UI version

2.12.0

OS/Browsers version

Windows 10 OS Version 1809 (Build 17763.805) / 78.0.3904.70 (Official Build) (64-bit) (cohort: 78_70_Win)

Vue version

2.6.10

Reproduction Link

https://codepen.io/mik-jozef-artin/pen/abbYNza

Steps to reproduce

  1. Open the reproduction link.
  2. Place mouse on the menu (ie. the text 'title')

The error 'Maximum call stack size exceeded' will appear in the console.

It seems to me that these conditions are all necessary for the issue to manifest:

  1. Menu must be collapsed (the collapse prop on el-menu must be true)
  2. Submenu must be in a separate component (in this case customized-menu-item)
  3. The submenu must be the top element in the separate component.
  4. Attribute :popper-append-to-body="true" must be present on the submenu

Fortunately, the second condition provides an easy workaround - placing the submenu inside a div
will suppress the issue. This behavior is still surprising and I guess unwanted.

What is Expected?

No errors.

What is actually happening?

Uncaught RangeError: Maximum call stack size exceeded.

Most helpful comment

I fixed this bug by rewirte method "handleMouseenter" of el-submenu in self-module .
(sorry for the format, I can't fix it.)

The easiest way is setting :popper-append-to-body="false" . If you have to append to body, you can try the follow code :

mounted() {
let redefine_el_submenu_handleMouseenter = function (event) {
// ignore some original handleMouseenter code, please parse by yourself.//
if (this.appendToBody && this.$parent.$el !== event.target) {
this.$parent.$el.dispatchEvent(new MouseEvent('mouseenter'));
}
}
this.$children[0].handleMouseenter = (event) => {
redefine_el_submenu_handleMouseenter.call(this.$children[0], event)
}
}

All 9 comments

same issue!

@element-bot 希望可以修复这个问题 应用场景还是蛮多的

same issue

mark

I fixed this bug by rewirte method "handleMouseenter" of el-submenu in self-module .
(sorry for the format, I can't fix it.)

The easiest way is setting :popper-append-to-body="false" . If you have to append to body, you can try the follow code :

mounted() {
let redefine_el_submenu_handleMouseenter = function (event) {
// ignore some original handleMouseenter code, please parse by yourself.//
if (this.appendToBody && this.$parent.$el !== event.target) {
this.$parent.$el.dispatchEvent(new MouseEvent('mouseenter'));
}
}
this.$children[0].handleMouseenter = (event) => {
redefine_el_submenu_handleMouseenter.call(this.$children[0], event)
}
}

same issue

hope fix it ~~

Still unfixed oficially

出问题代码
SideBarMenu/index.vue代码:

<template>
    <el-menu
        :collapse="closedSideBar"
        background-color=""
        text-color=""
        active-text-color=""
        :default-active="'/home'"
        router
    >
        <side-bar-menu-item
            v-for="(routeItem, index) in routeList"
            :key="index"
            :route-item="routeItem"
        ></side-bar-menu-item>
    </el-menu>
</template>

<script>
import SideBarMenuItem from "./components/SideBarMenuItem";
import mixins from "../mixins";
export default {
    name: "SideBarMenu",
    mixins: [mixins],
    components: {
        SideBarMenuItem,
    },
    data() {
        return {
            routeList: [
                { path: "/home", title: "路由1" },
                {
                    path: "2",
                    title: "路由2",
                    children: [
                        { path: "/about", title: "路由2-1" },
                        {
                            path: "2-2",
                            title: "路由2-2",
                            children: [{ path: "2-2-1", title: "路由2-2-1" }],
                        },
                    ],
                },
            ],
        };
    },
};
</script>

<style lang="scss" scoped>
.el-menu {
    border: none;
}
</style>

SideBarMenu/components/SideBarMenuItem/index.vue代码:

<template>
    <el-submenu v-if="routeItem.children && routeItem.children.length" :index="routeItem.path">
        <template #title>
            <i class="el-icon-menu"></i>
            <span>{{ routeItem.title }}</span>
        </template>
        <side-bar-menu-item
            v-for="(item, index) in routeItem.children"
            :key="index"
            :route-item="item"
        ></side-bar-menu-item>
    </el-submenu>
    <el-menu-item v-else :index="routeItem.path">
        <i class="el-icon-menu"></i>
        <template #title>
            <span>{{ routeItem.title }}</span>
        </template>
    </el-menu-item>
</template>

<script>
export default {
    name: "SideBarMenuItem",
    props: {
        routeItem: {
            type: Object,
        },
    },
};
</script>

解决方案:
直接使用渲染函数渲染本问题可以完美解决

import Vue from "vue";
import mixins from "../mixins";

const createSideBarMenuItem = (createElement, routeList) => {
    return routeList.map((routeItem) => {
        if (routeItem.children && routeItem.children.length) {
            return createElement(
                "el-submenu",
                {
                    props: {
                        index: routeItem.path,
                    },
                },
                [
                    createElement(
                        "template",
                        {
                            slot: "title",
                        },
                        [
                            createElement("i", {
                                class: "el-icon-menu",
                            }),
                            createElement("span", {
                                domProps: {
                                    innerHTML: routeItem.title,
                                },
                            }),
                        ]
                    ),
                    createSideBarMenuItem(createElement, routeItem.children),
                ]
            );
        } else {
            return createElement(
                "el-menu-item",
                {
                    props: {
                        index: routeItem.path,
                    },
                },
                [
                    createElement("i", {
                        class: "el-icon-menu",
                    }),
                    createElement("span", {
                        slot: "title",
                        domProps: {
                            innerHTML: routeItem.title,
                        },
                    }),
                ]
            );
        }
    });
};

const SideBarMenu = Vue.component("SideBarMenu", {
    render(createElement) {
        return createElement(
            "el-menu",
            {
                style: {
                    border: "none",
                },
                mixins: [mixins],
                props: {
                    router: true,
                    collapse: this.closedSideBar,
                },
            },
            createSideBarMenuItem(createElement, this.routeList)
        );
    },
    props: {
        routeList: {
            type: Array,
            default: () => [
                { path: "/home", title: "路由1" },
                {
                    path: "2",
                    title: "路由2",
                    children: [
                        { path: "/about", title: "路由2-1" },
                        {
                            path: "2-2",
                            title: "路由2-2",
                            children: [{ path: "2-2-1", title: "路由2-2-1" }],
                        },
                    ],
                },
            ],
        },
        closedSideBar: {
            type: Boolean,
            default: true,
        },
    },
});

export default SideBarMenu;
Was this page helpful?
0 / 5 - 0 ratings