From e4939b767e9a8427e9ef059bbdad4f62fa6a3bd9 Mon Sep 17 00:00:00 2001 From: Harry Waye Date: Thu, 21 Oct 2021 09:35:34 +0100 Subject: [PATCH] chore(eslint): explicitly disable exhaustive deps on historical code (#6576) I haven't made any attept here to resolve anything here, just to explicitly acknowledge that these are issues that eslint has highlighted and we should ignore, for the sake of easily picking up new issues. If these are truely problematic, then I am assuming they should have been picked up as bugs with the system. This doesn't stop someone from coming in and reviewing the code in this pr retrospectively. --- .../Annotations/AnnotationMarker.tsx | 36 +++++---- .../components/DateFilter/DateFilterRange.tsx | 16 ++-- frontend/src/lib/components/FullScreen.tsx | 78 +++++++++--------- .../src/lib/components/PayCard/PayCard.tsx | 16 ++-- .../PropertyFilters/PathItemFilters.tsx | 14 ++-- .../components/PropertyValue.tsx | 32 +++++--- .../lib/components/ResizableTable/index.tsx | 52 ++++++------ .../src/lib/components/RestrictedArea.tsx | 50 ++++++------ frontend/src/lib/components/SelectBox.tsx | 80 ++++++++++--------- frontend/src/lib/hooks/useD3.ts | 16 ++-- frontend/src/lib/hooks/useEscapeKey.js | 26 +++--- frontend/src/lib/hooks/useEventListener.ts | 1 + frontend/src/lib/hooks/useLongPress.js | 28 ++++--- .../src/lib/hooks/useOutsideClickHandler.ts | 34 ++++---- frontend/src/lib/hooks/useSecondRender.js | 16 ++-- frontend/src/lib/hooks/useWindowSize.js | 24 +++--- frontend/src/lib/storybook/kea-story.tsx | 22 ++--- frontend/src/lib/utils/responsiveUtils.tsx | 16 ++-- .../src/scenes/dashboard/DashboardHeader.tsx | 20 +++-- .../src/scenes/dashboard/DashboardItem.tsx | 14 ++-- frontend/src/scenes/dashboard/Dashboards.tsx | 24 +++--- frontend/src/scenes/events/EventsTable.tsx | 4 + frontend/src/scenes/events/VolumeTable.tsx | 34 ++++---- .../insights/ActionFilter/ActionFilter.tsx | 10 ++- .../insights/ActionFilter/RenameModal.tsx | 20 +++-- .../scenes/insights/Histogram/Histogram.tsx | 22 ++--- .../InsightHistoryPanel.tsx | 14 ++-- .../scenes/insights/LineGraph/LineGraph.js | 56 ++++++++----- .../insights/__stories__/Trends.stories.tsx | 64 +++++++++++++++ .../organization/Settings/BulkInviteModal.tsx | 14 ++-- frontend/src/scenes/paths/NewPaths.tsx | 12 ++- frontend/src/scenes/paths/Paths.js | 10 ++- frontend/src/scenes/persons/MergePerson.tsx | 10 ++- frontend/src/scenes/persons/PersonCohorts.tsx | 14 ++-- frontend/src/scenes/persons/Persons.tsx | 16 ++-- frontend/src/scenes/persons/PersonsSearch.tsx | 10 ++- .../src/scenes/plugins/edit/PluginDrawer.tsx | 30 ++++--- .../src/scenes/plugins/edit/PluginSource.tsx | 28 ++++--- .../src/scenes/plugins/plugin/PluginImage.tsx | 16 ++-- .../session-recordings/player/PlayerFrame.tsx | 24 +++--- .../player/SessionRecordingPlayerV2.tsx | 18 +++-- .../src/scenes/sessions/SessionDetails.tsx | 14 ++-- .../sessions/SessionRecordingsButton.tsx | 1 + frontend/src/scenes/sessions/SessionsPlay.tsx | 14 ++-- .../trends/viz/ActionsBarValueGraph.tsx | 14 ++-- frontend/src/scenes/trends/viz/ActionsPie.tsx | 14 ++-- frontend/src/toolbar/actions/EditAction.tsx | 32 ++++---- frontend/src/toolbar/button/ToolbarButton.tsx | 34 ++++---- 48 files changed, 720 insertions(+), 444 deletions(-) create mode 100644 frontend/src/scenes/insights/__stories__/Trends.stories.tsx diff --git a/frontend/src/lib/components/Annotations/AnnotationMarker.tsx b/frontend/src/lib/components/Annotations/AnnotationMarker.tsx index d18413a6ef165..efd3db6d9d7a1 100644 --- a/frontend/src/lib/components/Annotations/AnnotationMarker.tsx +++ b/frontend/src/lib/components/Annotations/AnnotationMarker.tsx @@ -83,14 +83,18 @@ export function AnnotationMarker({ const visible = focused || (!dynamic && hovered) - useEffect(() => { - if (visible) { - reportAnnotationViewed(annotations) - } else { - reportAnnotationViewed(null) - /* We report a null value to cancel (if applicable) the report because the annotation was closed */ - } - }, [visible]) + useEffect( + () => { + if (visible) { + reportAnnotationViewed(annotations) + } else { + reportAnnotationViewed(null) + /* We report a null value to cancel (if applicable) the report because the annotation was closed */ + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [visible] + ) const { user } = useValues(userLogic) const { currentTeam } = useValues(teamLogic) @@ -119,12 +123,16 @@ export function AnnotationMarker({ closePopup() } - useEffect(() => { - document.addEventListener('mousedown', deselect) - return () => { - document.removeEventListener('mousedown', deselect) - } - }, []) + useEffect( + () => { + document.addEventListener('mousedown', deselect) + return () => { + document.removeEventListener('mousedown', deselect) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) if ( dynamic && diff --git a/frontend/src/lib/components/DateFilter/DateFilterRange.tsx b/frontend/src/lib/components/DateFilter/DateFilterRange.tsx index 3d2736486b7d7..86cc14ea0bc27 100644 --- a/frontend/src/lib/components/DateFilter/DateFilterRange.tsx +++ b/frontend/src/lib/components/DateFilter/DateFilterRange.tsx @@ -46,12 +46,16 @@ export function DateFilterRange(props: { } } - useEffect(() => { - window.addEventListener('mousedown', onClickOutside) - return () => { - window.removeEventListener('mousedown', onClickOutside) - } - }, [calendarOpen]) + useEffect( + () => { + window.addEventListener('mousedown', onClickOutside) + return () => { + window.removeEventListener('mousedown', onClickOutside) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [calendarOpen] + ) return (
diff --git a/frontend/src/lib/components/FullScreen.tsx b/frontend/src/lib/components/FullScreen.tsx index 2ca3ec95706e0..9a80cbbccb2cc 100644 --- a/frontend/src/lib/components/FullScreen.tsx +++ b/frontend/src/lib/components/FullScreen.tsx @@ -2,45 +2,24 @@ import { useEffect } from 'react' export function FullScreen({ onExit }: { onExit?: () => any }): null { const selector = 'aside.ant-layout-sider, .layout-top-content' - useEffect(() => { - const myClasses = window.document.querySelectorAll(selector) as NodeListOf + useEffect( + () => { + const myClasses = window.document.querySelectorAll(selector) as NodeListOf - for (let i = 0; i < myClasses.length; i++) { - myClasses[i].style.display = 'none' - } - - const handler = (): void => { - if (window.document.fullscreenElement === null) { - onExit?.() + for (let i = 0; i < myClasses.length; i++) { + myClasses[i].style.display = 'none' } - } - - try { - window.document.body.requestFullscreen().then(() => { - window.addEventListener('fullscreenchange', handler, false) - }) - } catch { - // will break on IE11 - } - - try { - window.dispatchEvent(new window.Event('scroll')) - window.dispatchEvent(new window.Event('resize')) - } catch { - // will break on IE11 - } - - return () => { - const elements = window.document.querySelectorAll(selector) as NodeListOf - - for (let i = 0; i < elements.length; i++) { - elements[i].style.display = 'block' + + const handler = (): void => { + if (window.document.fullscreenElement === null) { + onExit?.() + } } + try { - window.removeEventListener('fullscreenchange', handler, false) - if (window.document.fullscreenElement !== null) { - window.document.exitFullscreen() - } + window.document.body.requestFullscreen().then(() => { + window.addEventListener('fullscreenchange', handler, false) + }) } catch { // will break on IE11 } @@ -51,8 +30,33 @@ export function FullScreen({ onExit }: { onExit?: () => any }): null { } catch { // will break on IE11 } - } - }, []) + + return () => { + const elements = window.document.querySelectorAll(selector) as NodeListOf + + for (let i = 0; i < elements.length; i++) { + elements[i].style.display = 'block' + } + try { + window.removeEventListener('fullscreenchange', handler, false) + if (window.document.fullscreenElement !== null) { + window.document.exitFullscreen() + } + } catch { + // will break on IE11 + } + + try { + window.dispatchEvent(new window.Event('scroll')) + window.dispatchEvent(new window.Event('resize')) + } catch { + // will break on IE11 + } + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return null } diff --git a/frontend/src/lib/components/PayCard/PayCard.tsx b/frontend/src/lib/components/PayCard/PayCard.tsx index 54dc6ad268803..652d0a467ed75 100644 --- a/frontend/src/lib/components/PayCard/PayCard.tsx +++ b/frontend/src/lib/components/PayCard/PayCard.tsx @@ -37,12 +37,16 @@ export function PayCard({ title, caption, docsLink, identifier }: PayCardProps): reportPayGateDismissed(identifier) } - useEffect(() => { - if (!window.localStorage.getItem(storageKey)) { - setShown(true) - reportPayGateShown(identifier) - } - }, []) + useEffect( + () => { + if (!window.localStorage.getItem(storageKey)) { + setShown(true) + reportPayGateShown(identifier) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) if (!shown) { return null diff --git a/frontend/src/lib/components/PropertyFilters/PathItemFilters.tsx b/frontend/src/lib/components/PropertyFilters/PathItemFilters.tsx index af312a392f33f..3cd51ef22f5f3 100644 --- a/frontend/src/lib/components/PropertyFilters/PathItemFilters.tsx +++ b/frontend/src/lib/components/PropertyFilters/PathItemFilters.tsx @@ -34,11 +34,15 @@ export function PathItemFilters({ const { filters } = useValues(propertyFilterLogic(logicProps)) const { setFilter, remove, setFilters } = useActions(propertyFilterLogic(logicProps)) - useEffect(() => { - if (propertyFilters && !objectsEqual(propertyFilters, filters)) { - setFilters([...propertyFilters, {}]) - } - }, [propertyFilters]) + useEffect( + () => { + if (propertyFilters && !objectsEqual(propertyFilters, filters)) { + setFilters([...propertyFilters, {}]) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [propertyFilters] + ) return (
diff --git a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx index bc475b5f100c4..74cfa68e70584 100644 --- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx @@ -72,16 +72,20 @@ export function PropertyValue({ const autoCompleteRef = useRef(null) // update the input field if passed a new `value` prop - useEffect(() => { - if (!value) { - setInput('') - } else if (value !== input) { - const valueObject = options[propertyKey]?.values?.find((v) => v.id === value) - if (valueObject) { - setInput(toString(valueObject.name)) + useEffect( + () => { + if (!value) { + setInput('') + } else if (value !== input) { + const valueObject = options[propertyKey]?.values?.find((v) => v.id === value) + if (valueObject) { + setInput(toString(valueObject.name)) + } } - } - }, [value]) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [value] + ) const loadPropertyValues = useThrottledCallback((newInput) => { if (type === 'cohort') { @@ -119,9 +123,13 @@ export function PropertyValue({ } } - useEffect(() => { - loadPropertyValues('') - }, [propertyKey]) + useEffect( + () => { + loadPropertyValues('') + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [propertyKey] + ) useEffect(() => { if (input === '' && shouldBlur) { diff --git a/frontend/src/lib/components/ResizableTable/index.tsx b/frontend/src/lib/components/ResizableTable/index.tsx index c1c80167a1b08..ae09f046ea921 100644 --- a/frontend/src/lib/components/ResizableTable/index.tsx +++ b/frontend/src/lib/components/ResizableTable/index.tsx @@ -186,30 +186,34 @@ export function ResizableTable = any>({ }) }, [initialColumns]) - useLayoutEffect(() => { - // Calculate relative column widths (px) once the wrapper is mounted. - if (scrollWrapperRef.current) { - resizeObserver.observe(scrollWrapperRef.current) - const wrapperWidth = scrollWrapperRef.current.clientWidth - const gridBasis = columns.reduce((total, { span }) => total + span, 0) - const columnSpanWidth = getFullwidthColumnSize(wrapperWidth, gridBasis) - setColumns((cols) => { - const lastIndex = cols.length - const nextColumns = cols.map((column, index) => - index === lastIndex - ? column - : { - ...column, - width: Math.max(column.defaultWidth || columnSpanWidth * column.span, minColumnWidth), - } - ) - setHeaderColumns(nextColumns) - return nextColumns - }) - updateScrollGradient() - setHeaderShouldRender(true) - } - }, []) + useLayoutEffect( + () => { + // Calculate relative column widths (px) once the wrapper is mounted. + if (scrollWrapperRef.current) { + resizeObserver.observe(scrollWrapperRef.current) + const wrapperWidth = scrollWrapperRef.current.clientWidth + const gridBasis = columns.reduce((total, { span }) => total + span, 0) + const columnSpanWidth = getFullwidthColumnSize(wrapperWidth, gridBasis) + setColumns((cols) => { + const lastIndex = cols.length + const nextColumns = cols.map((column, index) => + index === lastIndex + ? column + : { + ...column, + width: Math.max(column.defaultWidth || columnSpanWidth * column.span, minColumnWidth), + } + ) + setHeaderColumns(nextColumns) + return nextColumns + }) + updateScrollGradient() + setHeaderShouldRender(true) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return (
diff --git a/frontend/src/lib/components/RestrictedArea.tsx b/frontend/src/lib/components/RestrictedArea.tsx index c885a8767d04a..62e9d2bf95006 100644 --- a/frontend/src/lib/components/RestrictedArea.tsx +++ b/frontend/src/lib/components/RestrictedArea.tsx @@ -32,32 +32,36 @@ export function RestrictedArea({ const { currentOrganization } = useValues(organizationLogic) const { currentTeam } = useValues(teamLogic) - const restrictionReason: null | string = useMemo(() => { - let scopeAccessLevel: EitherMembershipLevel | null - if (scope === RestrictionScope.Project) { - if (!currentTeam) { - return 'Loading current project…' + const restrictionReason: null | string = useMemo( + () => { + let scopeAccessLevel: EitherMembershipLevel | null + if (scope === RestrictionScope.Project) { + if (!currentTeam) { + return 'Loading current project…' + } + scopeAccessLevel = currentTeam.effective_membership_level + } else { + if (!currentOrganization) { + return 'Loading current organization…' + } + scopeAccessLevel = currentOrganization.membership_level } - scopeAccessLevel = currentTeam.effective_membership_level - } else { - if (!currentOrganization) { - return 'Loading current organization…' + if (scopeAccessLevel === null) { + return `You don't have access to the current ${scope}.` } - scopeAccessLevel = currentOrganization.membership_level - } - if (scopeAccessLevel === null) { - return `You don't have access to the current ${scope}.` - } - if (scopeAccessLevel < minimumAccessLevel) { - if (minimumAccessLevel === OrganizationMembershipLevel.Owner) { - return `This area is restricted to the ${scope} owner.` + if (scopeAccessLevel < minimumAccessLevel) { + if (minimumAccessLevel === OrganizationMembershipLevel.Owner) { + return `This area is restricted to the ${scope} owner.` + } + return `This area is restricted to ${scope} ${membershipLevelToName.get( + minimumAccessLevel + )}s and up. Your level is ${membershipLevelToName.get(scopeAccessLevel)}.` } - return `This area is restricted to ${scope} ${membershipLevelToName.get( - minimumAccessLevel - )}s and up. Your level is ${membershipLevelToName.get(scopeAccessLevel)}.` - } - return null - }, [currentOrganization]) + return null + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentOrganization] + ) return restrictionReason ? ( diff --git a/frontend/src/lib/components/SelectBox.tsx b/frontend/src/lib/components/SelectBox.tsx index c7ffb129d99ae..6feed993d025e 100644 --- a/frontend/src/lib/components/SelectBox.tsx +++ b/frontend/src/lib/components/SelectBox.tsx @@ -77,25 +77,29 @@ export function SelectBox({ onDismiss(e) } - useEffect(() => { - if (selectedItemKey) { - const allSources = data.map((item) => item.dataSource).flat() - setSelectedItem(allSources.filter((item) => item.key === selectedItemKey)[0] || null) - const offset = document.querySelector( - '.search-list [datakey="' + selectedItemKey + '"]' - )?.offsetTop - const searchListSelector = document.querySelector('.search-list') - if (offset && searchListSelector) { - searchListSelector.scrollTop = offset + useEffect( + () => { + if (selectedItemKey) { + const allSources = data.map((item) => item.dataSource).flat() + setSelectedItem(allSources.filter((item) => item.key === selectedItemKey)[0] || null) + const offset = document.querySelector( + '.search-list [datakey="' + selectedItemKey + '"]' + )?.offsetTop + const searchListSelector = document.querySelector('.search-list') + if (offset && searchListSelector) { + searchListSelector.scrollTop = offset + } } - } - document.addEventListener('mousedown', deselect) - document.addEventListener('keydown', onKeyDown) - return () => { - document.removeEventListener('mousedown', deselect) - document.removeEventListener('keydown', onKeyDown) - } - }, []) + document.addEventListener('mousedown', deselect) + document.addEventListener('keydown', onKeyDown) + return () => { + document.removeEventListener('mousedown', deselect) + document.removeEventListener('keydown', onKeyDown) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return (
@@ -147,24 +151,28 @@ export function SelectUnit({ lengthOfData += entry?.dataSource?.length || 0 }) - useEffect(() => { - const formattedData: Record = {} - const _hiddenData: Record = {} - const _groupTypes: string[] = [] - const currHidden = Object.keys(hiddenData) - Object.keys(items).forEach((groupName) => { - if (!currHidden.includes(groupName)) { - formattedData[groupName] = items[groupName].dataSource - } else { - formattedData[groupName] = [] - _hiddenData[groupName] = items[groupName].dataSource - } - _groupTypes.push(groupName) - }) - setGroupTypes(_groupTypes) - setData(formattedData) - setHiddenData(_hiddenData) - }, [lengthOfData]) + useEffect( + () => { + const formattedData: Record = {} + const _hiddenData: Record = {} + const _groupTypes: string[] = [] + const currHidden = Object.keys(hiddenData) + Object.keys(items).forEach((groupName) => { + if (!currHidden.includes(groupName)) { + formattedData[groupName] = items[groupName].dataSource + } else { + formattedData[groupName] = [] + _hiddenData[groupName] = items[groupName].dataSource + } + _groupTypes.push(groupName) + }) + setGroupTypes(_groupTypes) + setData(formattedData) + setHiddenData(_hiddenData) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [lengthOfData] + ) useEffect(() => { const _flattenedData: SelectedItem[] = [] diff --git a/frontend/src/lib/hooks/useD3.ts b/frontend/src/lib/hooks/useD3.ts index 40507ffbfefa6..5f18dba4aa297 100644 --- a/frontend/src/lib/hooks/useD3.ts +++ b/frontend/src/lib/hooks/useD3.ts @@ -10,11 +10,15 @@ export const useD3 = ( ): MutableRefObject | null => { const ref = useRef() - useEffect(() => { - if (ref.current !== undefined) { - renderChartFn(d3.select(ref.current)) - } - return () => {} - }, dependencies) + useEffect( + () => { + if (ref.current !== undefined) { + renderChartFn(d3.select(ref.current)) + } + return () => {} + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + dependencies + ) return ref } diff --git a/frontend/src/lib/hooks/useEscapeKey.js b/frontend/src/lib/hooks/useEscapeKey.js index 5a5cddf4a30dd..32a9015da56a2 100644 --- a/frontend/src/lib/hooks/useEscapeKey.js +++ b/frontend/src/lib/hooks/useEscapeKey.js @@ -1,14 +1,22 @@ import { useCallback, useEffect } from 'react' export function useEscapeKey(callback, deps = []) { - const escFunction = useCallback((event) => { - if (event.keyCode === 27) { - callback() - } - }, deps) + const escFunction = useCallback( + (event) => { + if (event.keyCode === 27) { + callback() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + deps + ) - useEffect(() => { - document.addEventListener('keydown', escFunction, false) - return () => document.removeEventListener('keydown', escFunction, false) - }, deps) + useEffect( + () => { + document.addEventListener('keydown', escFunction, false) + return () => document.removeEventListener('keydown', escFunction, false) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + deps + ) } diff --git a/frontend/src/lib/hooks/useEventListener.ts b/frontend/src/lib/hooks/useEventListener.ts index 748e569a02736..3d87d16d8fd9a 100644 --- a/frontend/src/lib/hooks/useEventListener.ts +++ b/frontend/src/lib/hooks/useEventListener.ts @@ -48,6 +48,7 @@ export function useEventListener( element?.removeEventListener(eventName, eventListener) } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [eventName, element, ...(deps || [])] // Re-run if eventName or element changes ) } diff --git a/frontend/src/lib/hooks/useLongPress.js b/frontend/src/lib/hooks/useLongPress.js index d1dfbd42dec89..7df44425e73a6 100644 --- a/frontend/src/lib/hooks/useLongPress.js +++ b/frontend/src/lib/hooks/useLongPress.js @@ -30,19 +30,23 @@ export function useLongPress( const [startLongPress, setStartLongPress] = useState(null) const [initialCoords, setInitialCoords] = useState(null) - useEffect(() => { - let timerId - if (startLongPress && ms) { - timerId = setTimeout(() => { - callback(false, window.performance.now() - startLongPress, initialCoords) - stop() - }, ms) - } + useEffect( + () => { + let timerId + if (startLongPress && ms) { + timerId = setTimeout(() => { + callback(false, window.performance.now() - startLongPress, initialCoords) + stop() + }, ms) + } - return () => { - clearTimeout(timerId) - } - }, [callback, ms, startLongPress]) + return () => { + clearTimeout(timerId) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [callback, ms, startLongPress] + ) function start(e) { if (exclude && e.target.matches(exclude)) { diff --git a/frontend/src/lib/hooks/useOutsideClickHandler.ts b/frontend/src/lib/hooks/useOutsideClickHandler.ts index 2932203a709af..9beeb1815c3fd 100644 --- a/frontend/src/lib/hooks/useOutsideClickHandler.ts +++ b/frontend/src/lib/hooks/useOutsideClickHandler.ts @@ -9,21 +9,25 @@ export function useOutsideClickHandler( ): void { const allRefs = (Array.isArray(refOrRefs) ? refOrRefs : [refOrRefs]).map((f) => f) - useEffect(() => { - function handleClick(event: Event): void { - if (exceptions.some((exception) => (event.target as Element).matches(exception))) { - return + useEffect( + () => { + function handleClick(event: Event): void { + if (exceptions.some((exception) => (event.target as Element).matches(exception))) { + return + } + if (allRefs.some((ref) => ref?.contains(event.target as Element))) { + return + } + handleClickOutside?.(event) } - if (allRefs.some((ref) => ref?.contains(event.target as Element))) { - return - } - handleClickOutside?.(event) - } - if (allRefs.length > 0) { - // only attach event listeners if there's something to track - document.addEventListener('mousedown', handleClick) - return () => document.removeEventListener('mousedown', handleClick) - } - }, [...allRefs, ...extraDeps]) + if (allRefs.length > 0) { + // only attach event listeners if there's something to track + document.addEventListener('mousedown', handleClick) + return () => document.removeEventListener('mousedown', handleClick) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [...allRefs, ...extraDeps] + ) } diff --git a/frontend/src/lib/hooks/useSecondRender.js b/frontend/src/lib/hooks/useSecondRender.js index 7b997898fe456..5707a3e9f78d2 100644 --- a/frontend/src/lib/hooks/useSecondRender.js +++ b/frontend/src/lib/hooks/useSecondRender.js @@ -3,12 +3,16 @@ import { useEffect, useState } from 'react' export function useSecondRender(callback) { const [secondRender, setSecondRender] = useState(false) - useEffect(() => { - requestAnimationFrame(() => { - setSecondRender(true) - callback() - }) - }, []) + useEffect( + () => { + requestAnimationFrame(() => { + setSecondRender(true) + callback() + }) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return secondRender } diff --git a/frontend/src/lib/hooks/useWindowSize.js b/frontend/src/lib/hooks/useWindowSize.js index d8163b192fc54..e996e2bc82b59 100644 --- a/frontend/src/lib/hooks/useWindowSize.js +++ b/frontend/src/lib/hooks/useWindowSize.js @@ -12,18 +12,22 @@ export function useWindowSize() { const [windowSize, setWindowSize] = useState(getSize) - useEffect(() => { - if (!isClient) { - return false - } + useEffect( + () => { + if (!isClient) { + return false + } - function handleResize() { - setWindowSize(getSize()) - } + function handleResize() { + setWindowSize(getSize()) + } - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - }, []) // Empty array ensures that effect is only run on mount and unmount + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] // Empty array ensures that effect is only run on mount and unmount + ) return windowSize } diff --git a/frontend/src/lib/storybook/kea-story.tsx b/frontend/src/lib/storybook/kea-story.tsx index e8421d9b2ff02..b4c96743fda05 100644 --- a/frontend/src/lib/storybook/kea-story.tsx +++ b/frontend/src/lib/storybook/kea-story.tsx @@ -28,15 +28,19 @@ export function KeaStory({ }): T | JSX.Element | null { const [lastState, setLastState] = useState(null as Record | null) - useEffect(() => { - if (state !== lastState) { - setLastState(null) - } - if (state && lastState === null) { - resetKeaWithState(state) - setLastState(state) - } - }, [state]) + useEffect( + () => { + if (state !== lastState) { + setLastState(null) + } + if (state && lastState === null) { + resetKeaWithState(state) + setLastState(state) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [state] + ) return lastState ? {children || } : null } diff --git a/frontend/src/lib/utils/responsiveUtils.tsx b/frontend/src/lib/utils/responsiveUtils.tsx index 973c225f76ebc..85665ce9c6d53 100644 --- a/frontend/src/lib/utils/responsiveUtils.tsx +++ b/frontend/src/lib/utils/responsiveUtils.tsx @@ -38,12 +38,16 @@ interface ResizeObserverProps { export function useResizeObserver({ callback, element }: ResizeObserverProps): void { const observer = useRef(null) - useEffect(() => { - unobserve() - observer.current = new ResizeObserver(callback) - observe() - return unobserve - }, [element.current]) + useEffect( + () => { + unobserve() + observer.current = new ResizeObserver(callback) + observe() + return unobserve + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [element.current] + ) function observe(): void { if (element?.current && observer?.current) { diff --git a/frontend/src/scenes/dashboard/DashboardHeader.tsx b/frontend/src/scenes/dashboard/DashboardHeader.tsx index 6bc173f58f84c..da0b37e45abc6 100644 --- a/frontend/src/scenes/dashboard/DashboardHeader.tsx +++ b/frontend/src/scenes/dashboard/DashboardHeader.tsx @@ -164,15 +164,19 @@ export function DashboardHeader(): JSX.Element { ) - useEffect(() => { - if (dashboardMode === DashboardMode.Edit) { - if (lastDashboardModeSource === DashboardEventSource.AddDescription) { - setTimeout(() => descriptionInputRef.current?.focus(), 10) - } else if (!isMobile()) { - setTimeout(() => nameInputRef.current?.focus(), 10) + useEffect( + () => { + if (dashboardMode === DashboardMode.Edit) { + if (lastDashboardModeSource === DashboardEventSource.AddDescription) { + setTimeout(() => descriptionInputRef.current?.focus(), 10) + } else if (!isMobile()) { + setTimeout(() => nameInputRef.current?.focus(), 10) + } } - } - }, [dashboardMode]) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [dashboardMode] + ) return ( <> diff --git a/frontend/src/scenes/dashboard/DashboardItem.tsx b/frontend/src/scenes/dashboard/DashboardItem.tsx index ccdb4defd5a68..3586b3ac6dcfe 100644 --- a/frontend/src/scenes/dashboard/DashboardItem.tsx +++ b/frontend/src/scenes/dashboard/DashboardItem.tsx @@ -253,11 +253,15 @@ export function DashboardItem({ const diveDashboard = item.dive_dashboard ? getDashboard(item.dive_dashboard) : null // if a load is performed and returns that is not the initial load, we refresh dashboard item to update timestamp - useEffect(() => { - if (previousLoading && !insightLoading && !initialLoaded) { - setInitialLoaded(true) - } - }, [insightLoading]) + useEffect( + () => { + if (previousLoading && !insightLoading && !initialLoaded) { + setInitialLoaded(true) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [insightLoading] + ) // Empty states that completely replace the graph const BlockingEmptyState = (() => { diff --git a/frontend/src/scenes/dashboard/Dashboards.tsx b/frontend/src/scenes/dashboard/Dashboards.tsx index 6172ce48ea9c0..ccc4b150faf44 100644 --- a/frontend/src/scenes/dashboard/Dashboards.tsx +++ b/frontend/src/scenes/dashboard/Dashboards.tsx @@ -134,15 +134,21 @@ export function Dashboards(): JSX.Element { }, ] - useEffect(() => { - if (!hasAvailableFeature(AvailableFeature.DASHBOARD_COLLABORATION)) { - setDisplayedColumns( - columns.filter((col) => !col.dataIndex || !['description', 'tags'].includes(col.dataIndex.toString())) - ) - } else { - setDisplayedColumns(columns) - } - }, [user?.organization?.available_features, dashboardTags]) + useEffect( + () => { + if (!hasAvailableFeature(AvailableFeature.DASHBOARD_COLLABORATION)) { + setDisplayedColumns( + columns.filter( + (col) => !col.dataIndex || !['description', 'tags'].includes(col.dataIndex.toString()) + ) + ) + } else { + setDisplayedColumns(columns) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [user?.organization?.available_features, dashboardTags] + ) return (
diff --git a/frontend/src/scenes/events/EventsTable.tsx b/frontend/src/scenes/events/EventsTable.tsx index dc966fb8a89c0..b5528be51648a 100644 --- a/frontend/src/scenes/events/EventsTable.tsx +++ b/frontend/src/scenes/events/EventsTable.tsx @@ -237,6 +237,8 @@ export function EventsTable({ fixedFilters, filtersEnabled = true, pageKey }: Ev }, }, ] as ResizableColumnType[], + + // eslint-disable-next-line react-hooks/exhaustive-deps [eventFilter, tableWidth] ) @@ -274,6 +276,8 @@ export function EventsTable({ fixedFilters, filtersEnabled = true, pageKey }: Ev ellipsis: true, } ), + + // eslint-disable-next-line react-hooks/exhaustive-deps [selectedColumns] ) diff --git a/frontend/src/scenes/events/VolumeTable.tsx b/frontend/src/scenes/events/VolumeTable.tsx index 96bab4873e407..0de98e7d0ef82 100644 --- a/frontend/src/scenes/events/VolumeTable.tsx +++ b/frontend/src/scenes/events/VolumeTable.tsx @@ -167,21 +167,25 @@ export function VolumeTable({ : {}, ] - useEffect(() => { - setDataWithWarnings( - data.map((eventOrProp: EventOrPropType): VolumeTableRecord => { - const record = { eventOrProp } as VolumeTableRecord - record.warnings = [] - if (eventOrProp.name?.endsWith(' ')) { - record.warnings.push(`This ${type} ends with a space.`) - } - if (eventOrProp.name?.startsWith(' ')) { - record.warnings.push(`This ${type} starts with a space.`) - } - return record - }) || [] - ) - }, [data]) + useEffect( + () => { + setDataWithWarnings( + data.map((eventOrProp: EventOrPropType): VolumeTableRecord => { + const record = { eventOrProp } as VolumeTableRecord + record.warnings = [] + if (eventOrProp.name?.endsWith(' ')) { + record.warnings.push(`This ${type} ends with a space.`) + } + if (eventOrProp.name?.startsWith(' ')) { + record.warnings.push(`This ${type} starts with a space.`) + } + return record + }) || [] + ) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [data] + ) return ( <> diff --git a/frontend/src/scenes/insights/ActionFilter/ActionFilter.tsx b/frontend/src/scenes/insights/ActionFilter/ActionFilter.tsx index 7d808fd431236..a782ded3075f9 100644 --- a/frontend/src/scenes/insights/ActionFilter/ActionFilter.tsx +++ b/frontend/src/scenes/insights/ActionFilter/ActionFilter.tsx @@ -104,9 +104,13 @@ export const ActionFilter = React.forwardRef( // No way around this. Somehow the ordering of the logic calling each other causes stale "localFilters" // to be shown on the /funnels page, even if we try to use a selector with props to hydrate it - useEffect(() => { - setLocalFilters(filters) - }, [filters]) + useEffect( + () => { + setLocalFilters(filters) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [filters] + ) function onSortEnd({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void { function move(arr: LocalFilter[], from: number, to: number): LocalFilter[] { diff --git a/frontend/src/scenes/insights/ActionFilter/RenameModal.tsx b/frontend/src/scenes/insights/ActionFilter/RenameModal.tsx index 860f738b955cf..c675eb8e271b3 100644 --- a/frontend/src/scenes/insights/ActionFilter/RenameModal.tsx +++ b/frontend/src/scenes/insights/ActionFilter/RenameModal.tsx @@ -68,12 +68,16 @@ function useSelectAllText( ): void { // Hacky setTimeout is needed to select all text on modal open // https://github.com/ant-design/ant-design/issues/8668#issuecomment-352955313 - useEffect(() => { - const autoFocusTimeout = setTimeout(() => { - if (ref.current) { - ref.current?.focus(options) - } - }, 0) - return () => clearTimeout(autoFocusTimeout) - }, dependencies) + useEffect( + () => { + const autoFocusTimeout = setTimeout(() => { + if (ref.current) { + ref.current?.focus(options) + } + }, 0) + return () => clearTimeout(autoFocusTimeout) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + dependencies + ) } diff --git a/frontend/src/scenes/insights/Histogram/Histogram.tsx b/frontend/src/scenes/insights/Histogram/Histogram.tsx index 5537bb870a7b1..6acb3abe2f63e 100644 --- a/frontend/src/scenes/insights/Histogram/Histogram.tsx +++ b/frontend/src/scenes/insights/Histogram/Histogram.tsx @@ -79,15 +79,19 @@ export function Histogram({ const yAxisGrid = config.axisFn.y(y).tickSize(-config.gridlineTickSize).tickFormat('').ticks(y.ticks().length) // Update config to new values if dimensions change - useEffect(() => { - const minWidth = Math.max( - width, - data.length * (config.spacing.minBarWidth + config.spacing.btwnBins) + - config.margin.left + - config.margin.right - ) - setConfig(getConfig(layout, isDashboardItem ? width : minWidth, height)) - }, [data.length, layout, width, height]) + useEffect( + () => { + const minWidth = Math.max( + width, + data.length * (config.spacing.minBarWidth + config.spacing.btwnBins) + + config.margin.left + + config.margin.right + ) + setConfig(getConfig(layout, isDashboardItem ? width : minWidth, height)) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [data.length, layout, width, height] + ) const ref = useD3( (container) => { diff --git a/frontend/src/scenes/insights/InsightHistoryPanel/InsightHistoryPanel.tsx b/frontend/src/scenes/insights/InsightHistoryPanel/InsightHistoryPanel.tsx index 6b4d934d4612c..01762e8cb4333 100644 --- a/frontend/src/scenes/insights/InsightHistoryPanel/InsightHistoryPanel.tsx +++ b/frontend/src/scenes/insights/InsightHistoryPanel/InsightHistoryPanel.tsx @@ -46,11 +46,15 @@ function InsightPane({ const { loadTeamInsights, loadSavedInsights, loadInsights, updateInsight } = useActions(insightHistoryLogic) const { duplicateDashboardItem } = useActions(dashboardItemsModel) - useEffect(() => { - loadInsights() - loadSavedInsights() - loadTeamInsights() - }, []) + useEffect( + () => { + loadInsights() + loadSavedInsights() + loadTeamInsights() + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return ( diff --git a/frontend/src/scenes/insights/LineGraph/LineGraph.js b/frontend/src/scenes/insights/LineGraph/LineGraph.js index f389f5952790b..467bf3325bce3 100644 --- a/frontend/src/scenes/insights/LineGraph/LineGraph.js +++ b/frontend/src/scenes/insights/LineGraph/LineGraph.js @@ -75,9 +75,13 @@ export function LineGraph({ useEscapeKey(() => setFocused(false), [focused]) - useEffect(() => { - buildChart() - }, [datasets, color, visibilityMap]) + useEffect( + () => { + buildChart() + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [datasets, color, visibilityMap] + ) // Hacky! - Chartjs doesn't internally call tooltip callback on mouseout from right border. // Let's manually remove tooltips when the chart is being hovered over. #5061 @@ -96,13 +100,17 @@ export function LineGraph({ // annotation related effects // update boundaries and axis padding when user hovers with mouse or annotations load - useEffect(() => { - if (annotationsCondition && myLineChart.current) { - myLineChart.current.options.scales.xAxes[0].ticks.padding = annotationInRange || focused ? 35 : 0 - myLineChart.current.update() - calculateBoundaries() - } - }, [annotationsLoading, annotationsCondition, annotationsList, annotationInRange]) + useEffect( + () => { + if (annotationsCondition && myLineChart.current) { + myLineChart.current.options.scales.xAxes[0].ticks.padding = annotationInRange || focused ? 35 : 0 + myLineChart.current.update() + calculateBoundaries() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [annotationsLoading, annotationsCondition, annotationsList, annotationInRange] + ) useEffect(() => { if (annotationsCondition && datasets?.[0]?.days?.length > 0) { @@ -115,18 +123,26 @@ export function LineGraph({ }, [datasets, annotationsList, annotationsCondition]) // recalculate diff if interval type selection changes - useEffect(() => { - if (annotationsCondition && datasets?.[0]?.days) { - updateDiffType(datasets[0].days) - } - }, [datasets, type, annotationsCondition]) + useEffect( + () => { + if (annotationsCondition && datasets?.[0]?.days) { + updateDiffType(datasets[0].days) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [datasets, type, annotationsCondition] + ) // update only boundaries when window size changes or chart type changes - useEffect(() => { - if (annotationsCondition) { - calculateBoundaries() - } - }, [myLineChart.current, size, type, annotationsCondition]) + useEffect( + () => { + if (annotationsCondition) { + calculateBoundaries() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [myLineChart.current, size, type, annotationsCondition] + ) function calculateBoundaries() { const boundaryLeftExtent = myLineChart.current.scales['x-axis-0'].left diff --git a/frontend/src/scenes/insights/__stories__/Trends.stories.tsx b/frontend/src/scenes/insights/__stories__/Trends.stories.tsx new file mode 100644 index 0000000000000..d38b740593d74 --- /dev/null +++ b/frontend/src/scenes/insights/__stories__/Trends.stories.tsx @@ -0,0 +1,64 @@ +import { Meta } from '@storybook/react' + +import { keaStory } from 'lib/storybook/kea-story' +import { rest } from 'msw' +import { worker } from '~/mocks/browser' +import { Insights } from '../Insights' + +import trendsJson from './trends.json' + +export default { + title: 'PostHog/Scenes/Insights/Trends', +} as Meta + +export const TrendsSmoothing = (): JSX.Element => { + worker.use( + rest.post('/api/insight/trends', (_, res, ctx) => { + return res( + ctx.json({ + result: [ + { + action: { + id: '$pageview', + type: 'events', + order: 0, + name: '$pageview', + custom_name: null, + math: null, + math_property: null, + properties: [], + }, + label: '$pageview', + count: 181260.0, + data: [32944.0, 26552.0, 9385.0, 6905.0, 31078.0, 30918.0, 30434.0, 13044.0], + labels: [ + '7-Oct-2021', + '8-Oct-2021', + '9-Oct-2021', + '10-Oct-2021', + '11-Oct-2021', + '12-Oct-2021', + '13-Oct-2021', + '14-Oct-2021', + ], + days: [ + '2021-10-07', + '2021-10-08', + '2021-10-09', + '2021-10-10', + '2021-10-11', + '2021-10-12', + '2021-10-13', + '2021-10-14', + ], + }, + ], + last_refresh: '2021-10-14T11:13:28.176410Z', + is_cached: true, + next: null, + }) + ) + }) + ) + return keaStory(Insights, trendsJson)() +} diff --git a/frontend/src/scenes/organization/Settings/BulkInviteModal.tsx b/frontend/src/scenes/organization/Settings/BulkInviteModal.tsx index 147b80045fd92..d76c2f80c4e08 100644 --- a/frontend/src/scenes/organization/Settings/BulkInviteModal.tsx +++ b/frontend/src/scenes/organization/Settings/BulkInviteModal.tsx @@ -73,11 +73,15 @@ export function BulkInviteModal({ visible, onClose }: { visible: boolean; onClos const { invites, canSubmit, invitedTeamMembersLoading, invitedTeamMembers } = useValues(bulkInviteLogic) const { appendInviteRow, resetInviteRows, inviteTeamMembers } = useActions(bulkInviteLogic) - useEffect(() => { - if (invitedTeamMembers.length) { - onClose() - } - }, [invitedTeamMembers]) + useEffect( + () => { + if (invitedTeamMembers.length) { + onClose() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [invitedTeamMembers] + ) const areInvitesCreatable = invites.length + 1 < MAX_INVITES_AT_ONCE const areInvitesDeletable = invites.length > 1 diff --git a/frontend/src/scenes/paths/NewPaths.tsx b/frontend/src/scenes/paths/NewPaths.tsx index e20a2b2209a83..8937975307aa1 100644 --- a/frontend/src/scenes/paths/NewPaths.tsx +++ b/frontend/src/scenes/paths/NewPaths.tsx @@ -53,10 +53,14 @@ export function NewPaths({ dashboardItemId = null, color = 'white' }: PathsProps const hasAdvancedPaths = user?.organization?.available_features?.includes(AvailableFeature.PATHS_ADVANCED) - useEffect(() => { - setPathItemCards([]) - renderPaths() - }, [paths, !pathsLoading, size, color]) + useEffect( + () => { + setPathItemCards([]) + renderPaths() + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [paths, !pathsLoading, size, color] + ) const createCanvas = (width: number, height: number): D3Selector => { return d3 diff --git a/frontend/src/scenes/paths/Paths.js b/frontend/src/scenes/paths/Paths.js index 256a0bdd58ee3..a2f9e98c74dcf 100644 --- a/frontend/src/scenes/paths/Paths.js +++ b/frontend/src/scenes/paths/Paths.js @@ -106,9 +106,13 @@ export function OldPaths({ dashboardItemId = null, color = 'white' }) { const { insightProps } = useValues(insightLogic) const { paths, loadedFilter, resultsLoading: pathsLoading } = useValues(pathsLogic(insightProps)) - useEffect(() => { - renderPaths() - }, [paths, !pathsLoading, size, color]) + useEffect( + () => { + renderPaths() + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [paths, !pathsLoading, size, color] + ) function renderPaths() { const elements = document diff --git a/frontend/src/scenes/persons/MergePerson.tsx b/frontend/src/scenes/persons/MergePerson.tsx index be10ffd4cf348..66c5ea5268560 100644 --- a/frontend/src/scenes/persons/MergePerson.tsx +++ b/frontend/src/scenes/persons/MergePerson.tsx @@ -23,9 +23,13 @@ export function MergePerson({ const { loadPersons, setListFilters } = useActions(personsLogic) const { persons } = useValues(personsLogic) - useEffect(() => { - loadPersons() - }, [person.distinct_ids, person.id]) + useEffect( + () => { + loadPersons() + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [person.distinct_ids, person.id] + ) return ( { - if (cohorts === null && !cohortsLoading) { - loadCohorts() - } - }, [cohorts, cohortsLoading]) + useEffect( + () => { + if (cohorts === null && !cohortsLoading) { + loadCohorts() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [cohorts, cohortsLoading] + ) if (cohortsLoading) { return diff --git a/frontend/src/scenes/persons/Persons.tsx b/frontend/src/scenes/persons/Persons.tsx index 6b3d542cec943..58bf88691b3ed 100644 --- a/frontend/src/scenes/persons/Persons.tsx +++ b/frontend/src/scenes/persons/Persons.tsx @@ -17,12 +17,16 @@ export function Persons({ cohort }: { cohort: CohortType }): JSX.Element { const { loadPersons, setListFilters } = useActions(personsLogic) const { persons, listFilters, personsLoading } = useValues(personsLogic) - useEffect(() => { - if (cohort) { - setListFilters({ cohort: cohort.id }) - loadPersons() - } - }, []) + useEffect( + () => { + if (cohort) { + setListFilters({ cohort: cohort.id }) + loadPersons() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return (
diff --git a/frontend/src/scenes/persons/PersonsSearch.tsx b/frontend/src/scenes/persons/PersonsSearch.tsx index 04c14c6fae77b..f473c7fa4f8c9 100644 --- a/frontend/src/scenes/persons/PersonsSearch.tsx +++ b/frontend/src/scenes/persons/PersonsSearch.tsx @@ -8,9 +8,13 @@ export const PersonsSearch = ({ autoFocus = true }: { autoFocus?: boolean }): JS const { exampleEmail, listFilters } = useValues(personsLogic) const [searchTerm, setSearchTerm] = useState('') - useEffect(() => { - setSearchTerm(listFilters.search) - }, []) + useEffect( + () => { + setSearchTerm(listFilters.search) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) return ( ([]) const [requiredFields, setRequiredFields] = useState([]) - useEffect(() => { - if (editingPlugin) { - form.setFieldsValue({ - ...(editingPlugin.pluginConfig.config || defaultConfigForPlugin(editingPlugin)), - __enabled: editingPlugin.pluginConfig.enabled, - ...editingPluginInitialChanges, - }) - generateApiKeysIfNeeded(form) - } else { - form.resetFields() - } - updateInvisibleAndRequiredFields() - }, [editingPlugin?.id, editingPlugin?.config_schema]) + useEffect( + () => { + if (editingPlugin) { + form.setFieldsValue({ + ...(editingPlugin.pluginConfig.config || defaultConfigForPlugin(editingPlugin)), + __enabled: editingPlugin.pluginConfig.enabled, + ...editingPluginInitialChanges, + }) + generateApiKeysIfNeeded(form) + } else { + form.resetFields() + } + updateInvisibleAndRequiredFields() + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [editingPlugin?.id, editingPlugin?.config_schema] + ) const updateInvisibleAndRequiredFields = (): void => { determineAndSetInvisibleFields() diff --git a/frontend/src/scenes/plugins/edit/PluginSource.tsx b/frontend/src/scenes/plugins/edit/PluginSource.tsx index c09291ea30598..abae0176d2607 100644 --- a/frontend/src/scenes/plugins/edit/PluginSource.tsx +++ b/frontend/src/scenes/plugins/edit/PluginSource.tsx @@ -57,18 +57,22 @@ export function PluginSource(): JSX.Element { const { setEditingSource, editPluginSource } = useActions(pluginsLogic) const [form] = Form.useForm() - useEffect(() => { - if (editingPlugin) { - const newPlugin = !editingPlugin.source && Object.keys(editingPlugin.config_schema).length === 0 - form.setFieldsValue({ - name: editingPlugin.name || 'Untitled Plugin', - source: newPlugin ? defaultSource : editingPlugin.source, - configSchema: JSON.stringify(newPlugin ? defaultConfig : editingPlugin.config_schema, null, 2), - }) - } else { - form.resetFields() - } - }, [editingPlugin?.id, editingSource]) + useEffect( + () => { + if (editingPlugin) { + const newPlugin = !editingPlugin.source && Object.keys(editingPlugin.config_schema).length === 0 + form.setFieldsValue({ + name: editingPlugin.name || 'Untitled Plugin', + source: newPlugin ? defaultSource : editingPlugin.source, + configSchema: JSON.stringify(newPlugin ? defaultConfig : editingPlugin.config_schema, null, 2), + }) + } else { + form.resetFields() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [editingPlugin?.id, editingSource] + ) function savePluginSource(values: any): void { if (editingPlugin) { diff --git a/frontend/src/scenes/plugins/plugin/PluginImage.tsx b/frontend/src/scenes/plugins/plugin/PluginImage.tsx index 221900b542e59..27a6eca252468 100644 --- a/frontend/src/scenes/plugins/plugin/PluginImage.tsx +++ b/frontend/src/scenes/plugins/plugin/PluginImage.tsx @@ -16,12 +16,16 @@ export function PluginImage({ const [state, setState] = useState({ image: imgPluginDefault }) const pixelSize = size === 'large' ? 100 : 60 - useEffect(() => { - if (url?.includes('github.com')) { - const { user, repo } = parseGithubRepoURL(url) - setState({ ...state, image: `https://raw.githubusercontent.com/${user}/${repo}/main/logo.png` }) - } - }, [url]) + useEffect( + () => { + if (url?.includes('github.com')) { + const { user, repo } = parseGithubRepoURL(url) + setState({ ...state, image: `https://raw.githubusercontent.com/${user}/${repo}/main/logo.png` }) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [url] + ) return pluginType === 'source' ? ( diff --git a/frontend/src/scenes/session-recordings/player/PlayerFrame.tsx b/frontend/src/scenes/session-recordings/player/PlayerFrame.tsx index 2f751f11862e5..f93e954737dad 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerFrame.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerFrame.tsx @@ -10,16 +10,20 @@ export const PlayerFrame = React.forwardRef(function PlayerFrame const { togglePlayPause } = useActions(sessionRecordingPlayerLogic) const frameRef = ref as MutableRefObject - useEffect(() => { - if (!replayer) { - return - } - - replayer.on('resize', updatePlayerDimensions as Handler) - window.addEventListener('resize', windowResize) - - return () => window.removeEventListener('resize', windowResize) - }, [replayer]) + useEffect( + () => { + if (!replayer) { + return + } + + replayer.on('resize', updatePlayerDimensions as Handler) + window.addEventListener('resize', windowResize) + + return () => window.removeEventListener('resize', windowResize) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [replayer] + ) const windowResize = (): void => { updatePlayerDimensions(replayDimensionRef.current) diff --git a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayerV2.tsx b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayerV2.tsx index 131003ba3ff50..7b249798bebd9 100644 --- a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayerV2.tsx +++ b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayerV2.tsx @@ -15,14 +15,18 @@ export function SessionRecordingPlayerV2(): JSX.Element { const frame = useRef(null) // Need useEffect to populate replayer on component paint - useEffect(() => { - if (frame.current && isPlayable) { - stopAnimation() - initReplayer(frame) + useEffect( + () => { + if (frame.current && isPlayable) { + stopAnimation() + initReplayer(frame) - return () => stopAnimation() - } - }, [frame, isPlayable]) + return () => stopAnimation() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [frame, isPlayable] + ) const toggleFullScreen = (): void => { if (screenfull.isEnabled && frame.current) { diff --git a/frontend/src/scenes/sessions/SessionDetails.tsx b/frontend/src/scenes/sessions/SessionDetails.tsx index 2f8b5954d8845..756d35577eff3 100644 --- a/frontend/src/scenes/sessions/SessionDetails.tsx +++ b/frontend/src/scenes/sessions/SessionDetails.tsx @@ -23,11 +23,15 @@ export function SessionDetails({ session }: { session: SessionType }): JSX.Eleme const events = filteredSessionEvents[session.global_session_id] const matchingEventIds = useMemo(() => new Set(session.matching_events || []), [session.matching_events]) - useEffect(() => { - if (!events) { - loadSessionEvents(session) - } - }, []) + useEffect( + () => { + if (!events) { + loadSessionEvents(session) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) const columns = [ { diff --git a/frontend/src/scenes/sessions/SessionRecordingsButton.tsx b/frontend/src/scenes/sessions/SessionRecordingsButton.tsx index e919cc2c28ecf..a03ca7a7c85fb 100644 --- a/frontend/src/scenes/sessions/SessionRecordingsButton.tsx +++ b/frontend/src/scenes/sessions/SessionRecordingsButton.tsx @@ -59,6 +59,7 @@ export function SessionRecordingsButton({ sessionRecordings, source }: SessionRe
) }, + // eslint-disable-next-line react-hooks/exhaustive-deps [sessionRecordings, setAreRecordingsShown] ) diff --git a/frontend/src/scenes/sessions/SessionsPlay.tsx b/frontend/src/scenes/sessions/SessionsPlay.tsx index 5fbc78a37d040..ff0448ccf60fa 100644 --- a/frontend/src/scenes/sessions/SessionsPlay.tsx +++ b/frontend/src/scenes/sessions/SessionsPlay.tsx @@ -79,11 +79,15 @@ export function SessionsPlay(): JSX.Element { } }, [addingTagShown]) - useEffect(() => { - if (shouldLoadSessionEvents && session) { - loadSessionEvents(session) - } - }, [session]) + useEffect( + () => { + if (shouldLoadSessionEvents && session) { + loadSessionEvents(session) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [session] + ) const seekEvent = (time: number): void => { setCurrentPlayerTime(time) diff --git a/frontend/src/scenes/trends/viz/ActionsBarValueGraph.tsx b/frontend/src/scenes/trends/viz/ActionsBarValueGraph.tsx index 9549f3392409b..97832a68ff6b5 100644 --- a/frontend/src/scenes/trends/viz/ActionsBarValueGraph.tsx +++ b/frontend/src/scenes/trends/viz/ActionsBarValueGraph.tsx @@ -57,11 +57,15 @@ export function ActionsBarValueGraph({ setTotal(_data.reduce((prev, item) => prev + item.aggregated_value, 0)) } - useEffect(() => { - if (results) { - updateData() - } - }, [results, color]) + useEffect( + () => { + if (results) { + updateData() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [results, color] + ) return data && total > 0 ? ( prev + item.aggregated_value, 0)) } - useEffect(() => { - if (results) { - updateData() - } - }, [results, color]) + useEffect( + () => { + if (results) { + updateData() + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [results, color] + ) return data ? ( data[0] && data[0].labels ? ( diff --git a/frontend/src/toolbar/actions/EditAction.tsx b/frontend/src/toolbar/actions/EditAction.tsx index dbf9c7e7ec87f..98eab11f4d825 100644 --- a/frontend/src/toolbar/actions/EditAction.tsx +++ b/frontend/src/toolbar/actions/EditAction.tsx @@ -20,20 +20,24 @@ export function EditAction(): JSX.Element { const { getFieldValue } = form - useEffect(() => { - // This sucks. We're storing the antd "form" object in kea in a reducer. Dispatching an action for it. - // That's so that the logic would be able to access the latest state of the form. - // There's another ugly hack with a `counter` selector in the actionsTabLogic as well, check it out :P - // - // I tried just saving the form's state in kea via `fields` && `onFieldsChange`, but that's in a funny - // format and doesn't update if the form is updated dynamically (`form.setFields(fields)` on inspect element). - // - // The solution is probably to control the form state better in the logic, for example by providing a - // default `fields` value (it's a bit of work) and making changes against that, not through `form.setFields`. - // - // Thanks for reading, the next coffee is on me! / Marius - setForm(form) - }, [form]) + useEffect( + () => { + // This sucks. We're storing the antd "form" object in kea in a reducer. Dispatching an action for it. + // That's so that the logic would be able to access the latest state of the form. + // There's another ugly hack with a `counter` selector in the actionsTabLogic as well, check it out :P + // + // I tried just saving the form's state in kea via `fields` && `onFieldsChange`, but that's in a funny + // format and doesn't update if the form is updated dynamically (`form.setFields(fields)` on inspect element). + // + // The solution is probably to control the form state better in the logic, for example by providing a + // default `fields` value (it's a bit of work) and making changes against that, not through `form.setFields`. + // + // Thanks for reading, the next coffee is on me! / Marius + setForm(form) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [form] + ) return (
diff --git a/frontend/src/toolbar/button/ToolbarButton.tsx b/frontend/src/toolbar/button/ToolbarButton.tsx index fc64e6e28243d..07cf7a4f63a4e 100644 --- a/frontend/src/toolbar/button/ToolbarButton.tsx +++ b/frontend/src/toolbar/button/ToolbarButton.tsx @@ -72,25 +72,29 @@ export function ToolbarButton(): JSX.Element { // (Should be removed when we want to roll this out broadly) const showFeatureFlags = featureFlags[FEATURE_FLAGS.TOOLBAR_FEATURE_FLAGS] - useEffect(() => { - globalMouseMove.current = function (e: MouseEvent): void { - const buttonDiv = getShadowRoot()?.getElementById('button-toolbar') - if (buttonDiv) { - const rect = buttonDiv.getBoundingClientRect() - const x = rect.left + rect.width / 2 - const y = rect.top + rect.height / 2 - const distance = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y)) + useEffect( + () => { + globalMouseMove.current = function (e: MouseEvent): void { + const buttonDiv = getShadowRoot()?.getElementById('button-toolbar') + if (buttonDiv) { + const rect = buttonDiv.getBoundingClientRect() + const x = rect.left + rect.width / 2 + const y = rect.top + rect.height / 2 + const distance = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y)) - const maxDistance = isAuthenticated ? 300 : 100 + const maxDistance = isAuthenticated ? 300 : 100 - if (distance >= maxDistance && toolbarButtonLogic.values.extensionPercentage !== 0) { - setExtensionPercentage(0) + if (distance >= maxDistance && toolbarButtonLogic.values.extensionPercentage !== 0) { + setExtensionPercentage(0) + } } } - } - window.addEventListener('mousemove', globalMouseMove.current) - return () => window.removeEventListener('mousemove', globalMouseMove.current) - }, [isAuthenticated]) + window.addEventListener('mousemove', globalMouseMove.current) + return () => window.removeEventListener('mousemove', globalMouseMove.current) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [isAuthenticated] + ) // using useLongPress for short presses (clicks) since it detects if the element was dragged (no click) or not (click) const clickEvents = useLongPress(