Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a2d3eb5

Browse files
committed
fix(CDropdown): prevent unnecessary re-rendering
1 parent 90d8100 commit a2d3eb5

File tree

3 files changed

+63
-62
lines changed

3 files changed

+63
-62
lines changed

‎packages/coreui-react/src/components/dropdown/CDropdown.tsx

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import React, { ElementType, forwardRef, HTMLAttributes, useEffect, useRef, useState } from 'react'
1+
import React, {
2+
ElementType,
3+
forwardRef,
4+
HTMLAttributes,
5+
useCallback,
6+
useEffect,
7+
useMemo,
8+
useRef,
9+
useState,
10+
} from 'react'
211
import PropTypes from 'prop-types'
312
import classNames from 'classnames'
413
import type { Options } from '@popperjs/core'
@@ -190,59 +199,63 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
190199
ref
191200
) => {
192201
const dropdownRef = useRef<HTMLDivElement>(null)
193-
const dropdownToggleRef = useRef<HTMLElement>(null)
194202
const dropdownMenuRef = useRef<HTMLDivElement | HTMLUListElement>(null)
195203
const forkedRef = useForkedRef(ref, dropdownRef)
204+
const [dropdownToggleElement, setDropdownToggleElement] = useState<HTMLElement | null>(null)
196205
const [_visible, setVisible] = useState(visible)
197206
const { initPopper, destroyPopper } = usePopper()
198207

199-
const Component = variant === 'nav-item' ? 'li' : as
208+
const dropdownToggleRef = useCallback((node: HTMLElement | null) => {
209+
if (node) {
210+
setDropdownToggleElement(node)
211+
}
212+
}, [])
200213

201-
// Disable popper if responsive aligment is set.
202-
if (typeof alignment === 'object') {
203-
popper = false
204-
}
214+
const allowPopperUse = popper && typeof alignment !== 'object'
215+
const Component = variant === 'nav-item' ? 'li' : as
205216

206217
const contextValues = {
207218
alignment,
208219
container,
209220
dark,
210-
dropdownToggleRef,
211221
dropdownMenuRef,
212-
popper,
222+
dropdownToggleRef,
223+
popper: allowPopperUse,
213224
portal,
214225
variant,
215226
visible: _visible,
216227
setVisible,
217228
}
218229

219-
const defaultPopperConfig = {
220-
modifiers: [
221-
{
222-
name: 'offset',
223-
options: {
224-
offset: offset,
230+
const computedPopperConfig: Partial<Options> = useMemo(() => {
231+
const defaultPopperConfig = {
232+
modifiers: [
233+
{
234+
name: 'offset',
235+
options: {
236+
offset,
237+
},
225238
},
226-
},
227-
],
228-
placement: getPlacement(placement, direction, alignment, isRTL(dropdownMenuRef.current)),
229-
}
239+
],
240+
placement: getPlacement(placement, direction, alignment, isRTL(dropdownMenuRef.current)),
241+
}
230242

231-
const computedPopperConfig: Partial<Options> = {
232-
...defaultPopperConfig,
233-
...(typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig),
234-
}
243+
return {
244+
...defaultPopperConfig,
245+
...(typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig),
246+
}
247+
}, [offset, placement, direction, alignment, popperConfig])
235248

236249
useEffect(() => {
237250
setVisible(visible)
238251
}, [visible])
239252

240253
useEffect(() => {
241-
const toggleElement = dropdownToggleRef.current
254+
const toggleElement = dropdownToggleElement
242255
const menuElement = dropdownMenuRef.current
243256

244257
if (_visible && toggleElement && menuElement) {
245-
if (popper) {
258+
if (allowPopperUse) {
246259
initPopper(toggleElement, menuElement, computedPopperConfig)
247260
}
248261

@@ -257,7 +270,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
257270
}
258271

259272
return () => {
260-
if (popper) {
273+
if (allowPopperUse) {
261274
destroyPopper()
262275
}
263276

@@ -269,14 +282,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
269282

270283
onHide?.()
271284
}
272-
}, [
273-
computedPopperConfig,
274-
destroyPopper,
275-
dropdownMenuRef,
276-
dropdownToggleRef,
277-
initPopper,
278-
_visible,
279-
])
285+
}, [dropdownToggleElement, _visible])
280286

281287
const handleKeydown = (event: KeyboardEvent) => {
282288
if (
@@ -304,11 +310,11 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
304310
}
305311

306312
const handleMouseUp = (event: Event) => {
307-
if (!dropdownToggleRef.current || !dropdownMenuRef.current) {
313+
if (!dropdownToggleElement || !dropdownMenuRef.current) {
308314
return
309315
}
310316

311-
if (dropdownToggleRef.current.contains(event.target as HTMLElement)) {
317+
if (dropdownToggleElement.contains(event.target as HTMLElement)) {
312318
return
313319
}
314320

‎packages/coreui-react/src/components/dropdown/CDropdownContext.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ export interface CDropdownContextProps {
55
alignment?: Alignments
66
container?: DocumentFragment | Element | (() => DocumentFragment | Element | null) | null
77
dark?: boolean
8-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9-
dropdownToggleRef: RefObject<any | null>
108
dropdownMenuRef: RefObject<HTMLDivElement | HTMLUListElement | null>
9+
dropdownToggleRef: (node: HTMLElement | null) => void
1110
setVisible: React.Dispatch<React.SetStateAction<boolean | undefined>>
1211
popper?: boolean
1312
portal?: boolean

‎packages/coreui-react/src/components/dropdown/CDropdownToggle.tsx

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -74,36 +74,32 @@ export const CDropdownToggle: FC<CDropdownToggleProps> = ({
7474
...(!rest.disabled && { ...triggers }),
7575
}
7676

77-
const Toggler = () => {
78-
if (custom && React.isValidElement(children)) {
79-
return (
80-
<>
81-
{React.cloneElement(children as React.ReactElement<any>, {
82-
'aria-expanded': visible,
83-
...(!rest.disabled && { ...triggers }),
84-
ref: dropdownToggleRef,
85-
})}
86-
</>
87-
)
88-
}
89-
90-
if (variant === 'nav-item' && navLink) {
91-
return (
92-
<a href="#" {...togglerProps} role="button" {...rest} ref={dropdownToggleRef}>
93-
{children}
94-
</a>
95-
)
96-
}
77+
if (custom && React.isValidElement(children)) {
78+
return (
79+
<>
80+
{React.cloneElement(children as React.ReactElement<any>, {
81+
'aria-expanded': visible,
82+
...(!rest.disabled && { ...triggers }),
83+
ref: dropdownToggleRef,
84+
})}
85+
</>
86+
)
87+
}
9788

89+
if (variant === 'nav-item' && navLink) {
9890
return (
99-
<CButton{...togglerProps} tabIndex={0} {...rest} ref={dropdownToggleRef}>
91+
<ahref="#"{...togglerProps} role="button" {...rest} ref={dropdownToggleRef}>
10092
{children}
101-
{split && <span className="visually-hidden">Toggle Dropdown</span>}
102-
</CButton>
93+
</a>
10394
)
10495
}
10596

106-
return <Toggler />
97+
return (
98+
<CButton {...togglerProps} tabIndex={0} {...rest} ref={dropdownToggleRef}>
99+
{children}
100+
{split && <span className="visually-hidden">Toggle Dropdown</span>}
101+
</CButton>
102+
)
107103
}
108104

109105
CDropdownToggle.propTypes = {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /