I am attempting to make a responsive sidebar in Vue and when I refactored my project to remove the state.js file my dropdown/collapse elements I give this TypeError: Cannot read properties of null (reading 'scrollHeight')
that I am not sure how to solve.
I've read that
this error occurs when the scrollHeight property is accessed on an undefined value. To solve the error, make sure to only access the scrollHeight property on valid HTML elements.
But I am using a template ref here to grab the element, so I'm not sure why it wouldn't be accessable. Also, I had this working before I did my refactor and I just cannot figure out a way to work through this error.
Any ideas would be greatly appreciated!
Cheers!
LangDropdown.vue (both dropdowns use the same update
event where the error occurs)
<template>
<div @blur="dropdownIsOpen = false">
<div @click="dropdownIsOpen = !dropdownIsOpen">
<div style="display: flex">
<!-- <img style="width: 34px" src="../assets/languages-icon.svg" alt="Change Language Icon" /> -->
<div style="width: 34px" >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 34 34">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_2210" data-name="Rectangle 2210" width="34" height="34" rx="5" transform="translate(-7 -5)" fill="#fff"/>
</clipPath>
</defs>
<g id="languageIcon" transform="translate(7 5)">
<g id="Group_2048" data-name="Group 2048" clip-path="url(#clip-path)">
<path id="Path_3921" data-name="Path 3921" d="M7.267,10A17.388,17.388,0,0,0,9.89,4.15H1.142a1.042,1.042,0,1,1-.056-2.08c.449-.009.9,0,1.348,0h3.8c0-.339,0-.665,0-.992A1.01,1.01,0,0,1,6.625.231.987.987,0,0,1,7.678.091a.936.936,0,0,1,.61.845c.02.365,0,.732,0,1.132h.234q2.429,0,4.859,0a1.068,1.068,0,0,1,1.09.72,1.035,1.035,0,0,1-.98,1.36c-.247.007-.493,0-.74,0h-.736a19.4,19.4,0,0,1-3.413,7.545c.17.19.338.375.5.564a1.007,1.007,0,0,1,.2,1.094A.986.986,0,0,1,8.432,14a1.009,1.009,0,0,1-.861-.354c-.094-.1-.186-.206-.292-.323-.1.109-.2.215-.3.319a20.039,20.039,0,0,1-5.393,3.981,1.044,1.044,0,0,1-1.521-.531,1,1,0,0,1,.41-1.213c.467-.289.96-.536,1.432-.818a17.068,17.068,0,0,0,3.911-3.22.186.186,0,0,0,.012-.286,20.012,20.012,0,0,1-2.169-3.8,1.06,1.06,0,0,1,.25-1.277,1,1,0,0,1,1.32-.01,1.467,1.467,0,0,1,.356.5A19.431,19.431,0,0,0,7,9.613c.079.121.163.239.265.39" transform="translate(0 0)" fill="#fff"/>
<path id="Subtraction_34" data-name="Subtraction 34" d="M1.045,12.441a1.017,1.017,0,0,1-.365-.068,1.148,1.148,0,0,1-.343-.208A1.016,1.016,0,0,1,0,11.327a.925.925,0,0,1,.079-.306c.166-.364.349-.728.527-1.079v0l.166-.33c1.338-2.674,2.8-5.6,4.472-8.934a1.335,1.335,0,0,1,.4-.508A.875.875,0,0,1,5.907.041,1.012,1.012,0,0,1,6.2,0a1.4,1.4,0,0,1,.2.015A.965.965,0,0,1,6.67.094a.923.923,0,0,1,.217.142A1.372,1.372,0,0,1,7.2.668C7.8,1.858,8.42,3.1,8.968,4.194l.017.034L9.607,5.47l.6,1.192.021.042c.673,1.344,1.37,2.735,2.052,4.11a2.577,2.577,0,0,1,.147.407h0c.019.063.039.129.06.192a1.237,1.237,0,0,1-.234.611,1.033,1.033,0,0,1-.223.218,1.015,1.015,0,0,1-.285.142,1.123,1.123,0,0,1-.339.053,1,1,0,0,1-.458-.109,1.02,1.02,0,0,1-.364-.317,1.9,1.9,0,0,1-.154-.261L10.3,11.5c-.333-.662-.678-1.347-1.01-2.025a.275.275,0,0,0-.1-.124.3.3,0,0,0-.155-.033h0l-2.729,0c-1.025,0-1.97,0-2.888,0h0a.288.288,0,0,0-.164.04.314.314,0,0,0-.1.131c-.394.8-.78,1.584-1.178,2.356a1.192,1.192,0,0,1-.177.256,1.06,1.06,0,0,1-.222.186A.991.991,0,0,1,1.045,12.441ZM6.223,3.349,5.267,5.262l-.081.162-.208.416-.7,1.407h3.9L7.457,5.818l-.215-.431L6.223,3.349Z" transform="translate(8.305 8.31)" fill="#fff"/>
</g>
</g>
</svg>
</div>
<div style="flex: 1; display: flex; justify-content: space-between">
<div v-if="!collapsed" >
<div>
{{ selected }}
</div>
<!-- <img src="../assets/chevron-down.svg" alt="" /> -->
<div >
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><polyline points="6 9 12 15 18 9"></polyline></svg>
</div>
</div>
</div>
</div>
<ul ref="bodyEl" :style="bodyStyle">
<li v-for="(option, i) of options" :key="i" @click="selectOption(option)">
{{ option }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { defineComponent, ref, watchEffect, onMounted, onBeforeUnmount } from "vue"
export default defineComponent({
props: {
options: { type: Array, required: true },
default: { type: String, required: true },
collapsed: { type: Boolean, default: false },
},
emits: 'input',
setup(props, context){
const selected = ref(props.default ? props.default : props.options.length > 0 ? props.options[0] : null)
const dropdownIsOpen = ref(true)
if (props.collapsed) dropdownIsOpen.value = false
const bodyEl = ref(null)
const bodyStyle = ref({})
const update = () => bodyStyle.value = {
maxHeight: `${dropdownIsOpen.value ? bodyEl.value.scrollHeight : 0}px`
}
watchEffect(update)
onMounted(()=> window.addEventListener('resize', update))
onBeforeUnmount(()=> window.removeEventListener('resize', update))
function selectOption(_option) {
selected.value = _option
dropdownIsOpen.value = false
context.emit("input", _option)
}
return {
selectOption,
selected,
dropdownIsOpen,
bodyEl,
bodyStyle
}
}
});
</script>
<style lang="sass">
._custom-select
position: relative
width: 100%
text-align: left
outline: none
font-size: 16px
border-radius: 6px
margin-bottom: 2em
&:hover
._icon,
._selected-option
background-color: var(--sidebar-item-hover)
._custom-select-inner
display: flex
flex-direction: column
justify-content: space-between
._selected-option
flex: 1
display: flex
justify-content: space-between
margin-left: 1em
text-align: left
border-radius: 6px
padding: 8px 18px
cursor: pointer
user-select: none
line-height: 26px
._icon
width: 24px
border-radius: 6px
cursor: pointer
._options
overflow: hidden
max-height: 0
padding: 0 1rem
transition: max-height .3s cubic-bezier(.4,0,.2,1)
margin-left: auto
list-style-type: none
> *
border-radius: 6px
text-align: left
cursor: pointer
user-select: none
padding: 6px
width: 100%
> *:hover
background-color: var( --sidebar-item-hover)
._custom-select ._options div:hover
background-color: var( --sidebar-item-hover)
</style>
CodePudding user response:
watchEffect
call the update
function before the DOM has been mounted, so bodyEl
will still be null in this case.
You can use the watch
method instead, and you can call update
method on onMounted
lifecycle.
I update the code, please check.