chore: add hook linter (#18909)

* chore: install eslint-plugin-react-hooks

* fix: login dependency list

* chore(wip): lint hooks

* chore(wip): hook linting

* chore: fix / comment lint rules

* chore: comment lint warnings
pull/18911/head
Andrew Watkins 2020-07-09 16:45:33 -07:00 committed by GitHub
parent a3be86b231
commit 8e922ed14e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 388 additions and 432 deletions

View File

@ -15,6 +15,7 @@ module.exports = {
},
extends: [
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier/react',
'prettier/@typescript-eslint',
'eslint:recommended',

View File

@ -12,17 +12,31 @@ describe('The Login Page', () => {
user = u
})
cy.setupUser()
cy.setupUser().then(({body}) => {
cy.wrap(body.org.id).as('orgID')
})
cy.visit('/')
})
it('can login', () => {
it('can login and logout', () => {
cy.getByInputName('username').type(user.username)
cy.getByInputName('password').type(user.password)
cy.get('button[type=submit]').click()
cy.getByTestID('tree-nav').should('exist')
cy.getByTestID('logout--button').click()
cy.getByTestID('signin-page--content').should('exist')
// try to access a protected route
cy.get<string>('@orgID').then(orgID => {
cy.visit(`/orgs/${orgID}`)
})
// assert that user is routed to signin
cy.getByTestID('signin-page--content').should('exist')
})
describe('login failure', () => {

View File

@ -97,6 +97,7 @@
"eslint-config-prettier": "^6.5.0",
"eslint-plugin-jest": "^23.0.2",
"eslint-plugin-react": "^7.16.0",
"eslint-plugin-react-hooks": "^4.0.5",
"express": "^4.14.0",
"file-loader": "^4.1.0",
"fork-ts-checker-webpack-plugin": "^1.4.3",

View File

@ -1,7 +1,7 @@
// Libraries
import {FC, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {useDispatch} from 'react-redux'
import {RouteComponentProps} from 'react-router-dom'
// APIs
import {postSignout} from 'src/client'
@ -12,36 +12,31 @@ import {CLOUD, CLOUD_URL, CLOUD_LOGOUT_PATH} from 'src/shared/constants'
// Components
import {reset} from 'src/shared/actions/flags'
type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps & RouteComponentProps
type Props = RouteComponentProps
const Logout: FC<Props> = ({history, resetFeatureFlags}) => {
const handleSignOut = async () => {
if (CLOUD) {
window.location.href = `${CLOUD_URL}${CLOUD_LOGOUT_PATH}`
return
} else {
const resp = await postSignout({})
if (resp.status !== 204) {
throw new Error(resp.data.message)
}
history.push(`/signin`)
}
}
const Logout: FC<Props> = ({history}) => {
const dispatch = useDispatch()
useEffect(() => {
resetFeatureFlags()
const handleSignOut = async () => {
if (CLOUD) {
window.location.href = `${CLOUD_URL}${CLOUD_LOGOUT_PATH}`
return
} else {
const resp = await postSignout({})
if (resp.status !== 204) {
throw new Error(resp.data.message)
}
history.push(`/signin`)
}
}
dispatch(reset())
handleSignOut()
}, [])
}, [dispatch, history])
return null
}
const mdtp = {
resetFeatureFlags: reset,
}
const connector = connect(null, mdtp)
export default connector(withRouter(Logout))
export default Logout

View File

@ -32,7 +32,7 @@ const AlertHistoryQueryParams: FC<Props & RouteComponentProps> = ({
},
history
)
}, [searchInput, historyType])
}, [searchInput, historyType, history])
return null
}

View File

@ -56,12 +56,12 @@ const ThresholdCondition: FC<Props> = ({
get(threshold, 'max', 100),
])
const min = get(threshold, 'value') || get(threshold, 'min', inputs[0])
const max = get(threshold, 'max', inputs[1])
useEffect(() => {
changeInputs([
get(threshold, 'value') || get(threshold, 'min', inputs[0]),
get(threshold, 'max', inputs[1]),
])
}, [threshold])
changeInputs([min, max])
}, [min, max])
const [yDomain] = useCheckYDomain(table.getColumn('_value', 'number'), [])

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FC, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
// Components
import {
@ -11,10 +11,7 @@ import {
} from '@influxdata/clockface'
// Actions
import {
checkBucketLimits as checkBucketLimitsAction,
LimitStatus,
} from 'src/cloud/actions/limits'
import {checkBucketLimits, LimitStatus} from 'src/cloud/actions/limits'
import {showOverlay, dismissOverlay} from 'src/overlays/actions/overlays'
// Utils
@ -28,14 +25,14 @@ type Props = ReduxProps
const CreateBucketButton: FC<Props> = ({
limitStatus,
checkBucketLimits,
onShowOverlay,
onDismissOverlay,
}) => {
const dispatch = useDispatch()
useEffect(() => {
// Check bucket limits when component mounts
checkBucketLimits()
}, [])
dispatch(checkBucketLimits())
}, [dispatch])
const limitExceeded = limitStatus === LimitStatus.EXCEEDED
const text = 'Create Bucket'
@ -77,7 +74,6 @@ const mstp = (state: AppState) => {
const mdtp = {
onShowOverlay: showOverlay,
onDismissOverlay: dismissOverlay,
checkBucketLimits: checkBucketLimitsAction,
}
const connector = connect(mstp, mdtp)

View File

@ -1,12 +1,12 @@
// Libraries
import React, {FC, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {get, sortBy} from 'lodash'
// Actions
import {
getDemoDataBucketMembership as getDemoDataBucketMembershipAction,
getDemoDataBuckets as getDemoDataBucketsAction,
getDemoDataBuckets,
} from 'src/cloud/actions/demodata'
// Components
@ -22,11 +22,11 @@ const DemoDataDropdown: FC<Props> = ({
ownBucketsByID,
demoDataBuckets,
getDemoDataBucketMembership,
getDemoDataBuckets,
}) => {
const dispatch = useDispatch()
useEffect(() => {
getDemoDataBuckets()
}, [])
dispatch(getDemoDataBuckets())
}, [dispatch])
if (!demoDataBuckets.length) {
return null
@ -109,7 +109,6 @@ const mstp = (state: AppState) => ({
const mdtp = {
getDemoDataBucketMembership: getDemoDataBucketMembershipAction,
getDemoDataBuckets: getDemoDataBucketsAction,
}
const connector = connect(mstp, mdtp)

View File

@ -5,9 +5,10 @@ import React, {
useState,
ChangeEvent,
FormEvent,
useCallback,
} from 'react'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {get} from 'lodash'
// Components
@ -35,7 +36,6 @@ import {OwnBucket} from 'src/types'
interface DispatchProps {
onUpdateBucket: typeof updateBucket
onNotify: typeof notify
}
type ReduxProps = ConnectedProps<typeof connector>
@ -43,23 +43,27 @@ type Props = ReduxProps & RouteComponentProps<{bucketID: string; orgID: string}>
const UpdateBucketOverlay: FunctionComponent<Props> = ({
onUpdateBucket,
onNotify,
match,
history,
}) => {
const {orgID, bucketID} = match.params
const dispatch = useDispatch()
const [bucketDraft, setBucketDraft] = useState<OwnBucket>(null)
const [loadingStatus, setLoadingStatus] = useState(RemoteDataState.Loading)
const [retentionSelection, setRetentionSelection] = useState(DEFAULT_SECONDS)
const handleClose = useCallback(() => {
history.push(`/orgs/${orgID}/load-data/buckets`)
}, [orgID, history])
useEffect(() => {
const fetchBucket = async () => {
const resp = await api.getBucket({bucketID})
if (resp.status !== 200) {
onNotify(getBucketFailed(bucketID, resp.data.message))
dispatch(notify(getBucketFailed(bucketID, resp.data.message)))
handleClose()
return
}
@ -74,7 +78,7 @@ const UpdateBucketOverlay: FunctionComponent<Props> = ({
setLoadingStatus(RemoteDataState.Done)
}
fetchBucket()
}, [bucketID])
}, [bucketID, handleClose, dispatch])
const handleChangeRetentionRule = (everySeconds: number): void => {
setBucketDraft({
@ -112,10 +116,6 @@ const UpdateBucketOverlay: FunctionComponent<Props> = ({
setBucketDraft({...bucketDraft, [key]: value})
}
const handleClose = () => {
history.push(`/orgs/${orgID}/load-data/buckets`)
}
const handleClickRename = () => {
history.push(`/orgs/${orgID}/load-data/buckets/${bucketID}/rename`)
}
@ -157,7 +157,6 @@ const UpdateBucketOverlay: FunctionComponent<Props> = ({
const mdtp = {
onUpdateBucket: updateBucket,
onNotify: notify,
}
const connector = connect(null, mdtp)

View File

@ -94,7 +94,7 @@ const CheckMatchingRulesCard: FC<Props> = ({orgID, tags, queryResults}) => {
})
getMatchingRules()
}, [tags, queryResults])
}, [tags, queryResults]) // eslint-disable-line react-hooks/exhaustive-deps
let contents: JSX.Element

View File

@ -1,7 +1,7 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {get} from 'lodash'
// Components
@ -30,8 +30,6 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
onUpdateAlertBuilderName,
onResetAlertBuilder,
onSaveCheckFromTimeMachine,
onExecuteQueries,
onGetCheckForTimeMachine,
activeTimeMachineID,
status,
history,
@ -42,13 +40,16 @@ const EditCheckEditorOverlay: FunctionComponent<Props> = ({
loadedCheckID,
view,
}) => {
useEffect(() => {
onGetCheckForTimeMachine(checkID)
}, [checkID])
const dispatch = useDispatch()
const query = get(view, 'properties.queries[0]', null)
useEffect(() => {
onExecuteQueries()
}, [get(view, 'properties.queries[0]', null)])
dispatch(getCheckForTimeMachine(checkID))
}, [dispatch, checkID])
useEffect(() => {
dispatch(executeQueries())
}, [dispatch, query])
const handleClose = () => {
history.push(`/orgs/${orgID}/alerting`)
@ -108,9 +109,7 @@ const mstp = (state: AppState) => {
}
const mdtp = {
onGetCheckForTimeMachine: getCheckForTimeMachine,
onSaveCheckFromTimeMachine: updateCheckFromTimeMachine,
onExecuteQueries: executeQueries,
onResetAlertBuilder: resetAlertBuilder,
onUpdateAlertBuilderName: updateName,
}

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
// Components
@ -34,18 +34,20 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
checkName,
history,
onSaveCheckFromTimeMachine,
onSetActiveTimeMachine,
onResetAlertBuilder,
onUpdateAlertBuilderName,
onInitializeAlertBuilder,
}) => {
const dispatch = useDispatch()
useEffect(() => {
const view = createView<CheckViewProperties>('deadman')
onInitializeAlertBuilder('deadman')
onSetActiveTimeMachine('alerting', {
view,
})
}, [])
dispatch(initializeAlertBuilder('deadman'))
dispatch(
setActiveTimeMachine('alerting', {
view,
})
)
}, [dispatch])
const handleClose = () => {
history.push(`/orgs/${orgID}/alerting`)
@ -80,11 +82,9 @@ const mstp = ({alertBuilder: {name, status}}: AppState) => {
}
const mdtp = {
onSetActiveTimeMachine: setActiveTimeMachine,
onSaveCheckFromTimeMachine: createCheckFromTimeMachine,
onResetAlertBuilder: resetAlertBuilder,
onUpdateAlertBuilderName: updateName,
onInitializeAlertBuilder: initializeAlertBuilder,
}
const connector = connect(mstp, mdtp)

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
// Components
@ -34,18 +34,20 @@ const NewCheckOverlay: FunctionComponent<Props> = ({
checkName,
history,
onSaveCheckFromTimeMachine,
onSetActiveTimeMachine,
onResetAlertBuilder,
onUpdateAlertBuilderName,
onInitializeAlertBuilder,
}) => {
const dispatch = useDispatch()
useEffect(() => {
const view = createView<CheckViewProperties>('threshold')
onInitializeAlertBuilder('threshold')
onSetActiveTimeMachine('alerting', {
view,
})
}, [])
dispatch(initializeAlertBuilder('threshold'))
dispatch(
setActiveTimeMachine('alerting', {
view,
})
)
}, [dispatch])
const handleClose = () => {
history.push(`/orgs/${orgID}/alerting`)
@ -80,11 +82,9 @@ const mstp = ({alertBuilder: {name, status}}: AppState) => {
}
const mdtp = {
onSetActiveTimeMachine: setActiveTimeMachine,
onSaveCheckFromTimeMachine: createCheckFromTimeMachine as any,
onResetAlertBuilder: resetAlertBuilder,
onUpdateAlertBuilderName: updateName,
onInitializeAlertBuilder: initializeAlertBuilder,
}
const connector = connect(mstp, mdtp)

View File

@ -1,6 +1,6 @@
// Libraries
import {FunctionComponent, useEffect, useState} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {FC, useEffect, useState} from 'react'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
// Constants
import {CLOUD} from 'src/shared/constants'
@ -22,21 +22,18 @@ interface PassedInProps {
type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps & PassedInProps
const OrgSettings: FunctionComponent<Props> = ({
org,
getOrgSettings,
settings,
children,
}) => {
const OrgSettings: FC<Props> = ({org, settings, children}) => {
const dispatch = useDispatch()
const [hasFetchedOrgSettings, setHasFetchedOrgSettings] = useState<boolean>(
false
)
useEffect(() => {
if (CLOUD && org && !hasFetchedOrgSettings) {
setHasFetchedOrgSettings(true)
getOrgSettings()
dispatch(getOrgSettingsAction())
}
}, [org])
}, [dispatch, org, hasFetchedOrgSettings])
useEffect(() => {
updateReportingContext(

View File

@ -166,5 +166,5 @@ export const useLoadTimeReporting = (event: string) => {
fields: {},
tags: {event},
})
}, [])
}, [event, loadStartTime])
}

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FC, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
// Components
import GetResource from 'src/resources/components/GetResource'
@ -26,11 +26,8 @@ const {Active} = AutoRefreshStatus
type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps
const DashboardContainer: FC<Props> = ({
autoRefresh,
dashboard,
onSetCurrentPage,
}) => {
const DashboardContainer: FC<Props> = ({autoRefresh, dashboard}) => {
const dispatch = useDispatch()
useEffect(() => {
if (autoRefresh.status === Active) {
GlobalAutoRefresher.poll(autoRefresh.interval)
@ -45,11 +42,11 @@ const DashboardContainer: FC<Props> = ({
}, [autoRefresh.status, autoRefresh.interval])
useEffect(() => {
onSetCurrentPage('dashboard')
dispatch(setCurrentPage('dashboard'))
return () => {
onSetCurrentPage('not set')
dispatch(setCurrentPage('not set'))
}
}, [])
}, [dispatch])
return (
<DashboardRoute>
@ -72,10 +69,6 @@ const mstp = (state: AppState) => {
}
}
const mdtp = {
onSetCurrentPage: setCurrentPage,
}
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(DashboardContainer)

View File

@ -74,7 +74,7 @@ const DashboardHeader: FC<Props> = ({
}) => {
useEffect(() => {
fireDashboardViewedEvent(dashboard.name)
}, [dashboard.id])
}, [dashboard.id]) // eslint-disable-line react-hooks/exhaustive-deps
const handleAddNote = () => {
history.push(`/orgs/${org.id}/dashboards/${dashboard.id}/notes/new`)

View File

@ -1,7 +1,7 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {get} from 'lodash'
// Components
@ -26,7 +26,6 @@ type Props = ReduxProps &
const EditViewVEO: FunctionComponent<Props> = ({
activeTimeMachineID,
getViewAndResultsForVEO,
onSaveView,
onSetName,
match: {
@ -35,12 +34,13 @@ const EditViewVEO: FunctionComponent<Props> = ({
history,
view,
}) => {
const dispatch = useDispatch()
useEffect(() => {
// TODO split this up into "loadView" "setActiveTimeMachine"
// and something to tell the component to pull from the context
// of the dashboardID
getViewAndResultsForVEO(dashboardID, cellID, 'veo')
}, [])
dispatch(getViewAndResultsForVEO(dashboardID, cellID, 'veo'))
}, [dispatch, dashboardID, cellID])
const handleClose = () => {
history.push(`/orgs/${orgID}/dashboards/${dashboardID}`)
@ -91,7 +91,6 @@ const mstp = (state: AppState) => {
}
const mdtp = {
getViewAndResultsForVEO: getViewAndResultsForVEO,
onSetName: setName,
onSaveView: saveVEOView,
}

View File

@ -1,25 +1,20 @@
// Libraries
import React, {FC, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {useDispatch, useSelector} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {getTimeRange} from 'src/dashboards/selectors'
// Actions
import * as actions from 'src/dashboards/actions/ranges'
// Types
import {AppState} from 'src/types'
type ReduxProps = ConnectedProps<typeof connector>
type Props = RouteComponentProps<{dashboardID: string}> & ReduxProps
const GetTimeRange: FC<Props> = ({
location,
match,
timeRange,
import {
setDashboardTimeRange,
updateQueryParams,
}: Props) => {
} from 'src/dashboards/actions/ranges'
type Props = RouteComponentProps<{dashboardID: string}>
const GetTimeRange: FC<Props> = ({location, match}: Props) => {
const dispatch = useDispatch()
const timeRange = useSelector(getTimeRange)
const isEditing = location.pathname.includes('edit')
const isNew = location.pathname.includes('new')
@ -29,27 +24,17 @@ const GetTimeRange: FC<Props> = ({
}
// TODO: map this to current contextID
setDashboardTimeRange(match.params.dashboardID, timeRange)
dispatch(setDashboardTimeRange(match.params.dashboardID, timeRange))
const {lower, upper} = timeRange
updateQueryParams({
lower,
upper,
})
}, [isEditing, isNew])
dispatch(
updateQueryParams({
lower,
upper,
})
)
}, [dispatch, isEditing, isNew, match.params.dashboardID, timeRange])
return <div />
}
const mstp = (state: AppState) => {
const timeRange = getTimeRange(state)
return {timeRange}
}
const mdtp = {
updateQueryParams: actions.updateQueryParams,
setDashboardTimeRange: actions.setDashboardTimeRange,
}
const connector = connect(mstp, mdtp)
export default withRouter(connector(GetTimeRange))
export default withRouter(GetTimeRange)

View File

@ -1,7 +1,7 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {get} from 'lodash'
// Components
@ -26,7 +26,6 @@ type Props = ReduxProps &
const NewViewVEO: FunctionComponent<Props> = ({
activeTimeMachineID,
onLoadNewVEO,
onSaveView,
onSetName,
match: {
@ -35,9 +34,10 @@ const NewViewVEO: FunctionComponent<Props> = ({
history,
view,
}) => {
const dispatch = useDispatch()
useEffect(() => {
onLoadNewVEO()
}, [dashboardID])
dispatch(loadNewVEO())
}, [dispatch, dashboardID])
const handleClose = () => {
history.push(`/orgs/${orgID}/dashboards/${dashboardID}`)
@ -47,7 +47,9 @@ const NewViewVEO: FunctionComponent<Props> = ({
try {
onSaveView(dashboardID)
handleClose()
} catch (e) {}
} catch (error) {
console.error(error)
}
}
let loadingState = RemoteDataState.Loading
@ -89,7 +91,6 @@ const mstp = (state: AppState) => {
const mdtp = {
onSetName: setName,
onSaveView: saveVEOView,
onLoadNewVEO: loadNewVEO,
}
const connector = connect(mstp, mdtp)

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FC, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {useDispatch} from 'react-redux'
// Components
import TimeMachine from 'src/timeMachine/components/TimeMachine'
@ -16,19 +16,15 @@ import {HoverTimeProvider} from 'src/dashboards/utils/hoverTime'
import {queryBuilderFetcher} from 'src/timeMachine/apis/QueryBuilderFetcher'
import {readQueryParams} from 'src/shared/utils/queryParams'
type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps
const DataExplorer: FC = () => {
const dispatch = useDispatch()
const DataExplorer: FC<Props> = ({
onSetActiveTimeMachine,
onSetBuilderBucketIfExists,
}) => {
useEffect(() => {
const bucketQP = readQueryParams()['bucket']
onSetActiveTimeMachine('de')
dispatch(setActiveTimeMachine('de'))
queryBuilderFetcher.clearCache()
onSetBuilderBucketIfExists(bucketQP)
}, [])
dispatch(setBuilderBucketIfExists(bucketQP))
}, [dispatch])
return (
<LimitChecker>
@ -42,11 +38,4 @@ const DataExplorer: FC<Props> = ({
)
}
const mdtp = {
onSetActiveTimeMachine: setActiveTimeMachine,
onSetBuilderBucketIfExists: setBuilderBucketIfExists,
}
const connector = connect(null, mdtp)
export default connector(DataExplorer)
export default DataExplorer

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {get} from 'lodash'
@ -33,24 +33,23 @@ const DeleteDataOverlay: FunctionComponent<Props> = ({
},
bucketNameFromDE,
timeRangeFromDE,
resetPredicateState,
setTimeRange,
setBucketAndKeys,
}) => {
const dispatch = useDispatch()
useEffect(() => {
if (bucketNameFromDE) {
setBucketAndKeys(bucketNameFromDE)
dispatch(setBucketAndKeys(bucketNameFromDE))
}
}, [bucketNameFromDE])
}, [dispatch, bucketNameFromDE])
useEffect(() => {
if (timeRangeFromDE) {
setTimeRange(convertTimeRangeToCustom(timeRangeFromDE))
dispatch(setTimeRange(convertTimeRangeToCustom(timeRangeFromDE)))
}
}, [timeRangeFromDE])
}, [dispatch, timeRangeFromDE])
const handleDismiss = () => {
resetPredicateState()
dispatch(resetPredicateState())
history.push(`/orgs/${orgID}/data-explorer`)
}
@ -80,12 +79,6 @@ const mstp = (state: AppState) => {
}
}
const mdtp = {
resetPredicateState,
setTimeRange,
setBucketAndKeys,
}
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(withRouter(DeleteDataOverlay))

View File

@ -1,6 +1,6 @@
// Libraries
import React, {useLayoutEffect, FC, useEffect, useState} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {useDispatch} from 'react-redux'
import {AutoSizer, InfiniteLoader, List} from 'react-virtualized'
// Components
@ -11,7 +11,7 @@ import FooterRow from 'src/eventViewer/components/FooterRow'
import ErrorRow from 'src/eventViewer/components/ErrorRow'
// Actions
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {notify} from 'src/shared/actions/notifications'
// Utils
import {
@ -30,13 +30,14 @@ type OwnProps = {
fields: Fields
}
type ReduxProps = ConnectedProps<typeof connector>
type Props = EventViewerChildProps & ReduxProps & OwnProps
type Props = EventViewerChildProps & OwnProps
const EventTable: FC<Props> = ({state, dispatch, loadRows, fields, notify}) => {
const rowLoadedFn = state => ({index}) => !!state.rows[index]
const EventTable: FC<Props> = ({state, dispatch, loadRows, fields}) => {
const rowCount = getRowCount(state)
const isRowLoaded = ({index}) => !!state.rows[index]
const isRowLoaded = rowLoadedFn(state)
const isRowLoadedBoolean = !!state.rows[0]
@ -44,6 +45,8 @@ const EventTable: FC<Props> = ({state, dispatch, loadRows, fields, notify}) => {
const [isLongRunningQuery, setIsLongRunningQuery] = useState(false)
const reduxDispatch = useDispatch()
useEffect(() => {
setTimeout(() => {
setIsLongRunningQuery(true)
@ -52,9 +55,9 @@ const EventTable: FC<Props> = ({state, dispatch, loadRows, fields, notify}) => {
useEffect(() => {
if (isLongRunningQuery && !isRowLoadedBoolean) {
notify(checkStatusLoading)
reduxDispatch(notify(checkStatusLoading))
}
}, [isLongRunningQuery, isRowLoaded])
}, [reduxDispatch, isLongRunningQuery, isRowLoadedBoolean, isRowLoaded])
const rowRenderer = ({key, index, style}) => {
const isLastRow = index === state.rows.length
@ -129,10 +132,4 @@ const EventTable: FC<Props> = ({state, dispatch, loadRows, fields, notify}) => {
)
}
const mdtp = {
notify: notifyAction,
}
const connector = connect(null, mdtp)
export default connector(EventTable)
export default EventTable

View File

@ -28,13 +28,17 @@ const EventViewer: FC<Props> = ({loadRows, children, initialState}) => {
useEffect(() => {
loadNextRows(state, dispatch, loadRows, Date.now())
}, [])
}, []) // eslint-disable-line react-hooks/exhaustive-deps
useMountedLayoutEffect(() => {
refresh(state, dispatch, loadRows)
}, [loadRows])
return children({state, dispatch, loadRows})
return children({
state,
dispatch,
loadRows,
})
}
export default EventViewer

View File

@ -8,7 +8,11 @@ import {Button, ComponentSize} from '@influxdata/clockface'
const LogoutButton: SFC = () => (
<>
<Link to="/logout">
<Button text="Logout" size={ComponentSize.ExtraSmall} />
<Button
text="Logout"
size={ComponentSize.ExtraSmall}
testID="logout--button"
/>
</Link>
</>
)

View File

@ -12,7 +12,7 @@ const Pipe: FC<PipeProp> = props => {
return useMemo(
() => createElement(PIPE_DEFINITIONS[data.type].component, props),
[props.data, props.results]
[props.data, props.results] // eslint-disable-line react-hooks/exhaustive-deps
)
}

View File

@ -13,7 +13,7 @@ import {DapperScrollbars} from '@influxdata/clockface'
const PipeList: FC = () => {
const {id, pipes, updatePipe, results, meta} = useContext(NotebookContext)
const {scrollPosition} = useContext(ScrollContext)
const update = useCallback(updatePipe, [id])
const update = useCallback(updatePipe, [id, updatePipe])
if (!pipes.length) {
return <EmptyPipeList />

View File

@ -1,4 +1,4 @@
import React, {FC, useMemo} from 'react'
import React, {FC, useMemo, useCallback} from 'react'
import {default as StatelessAutoRefreshDropdown} from 'src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown'
import {TimeContextProps} from 'src/notebooks/components/header/Buttons'
import {TimeBlock} from 'src/notebooks/context/time'
@ -10,32 +10,34 @@ import {event} from 'src/notebooks/shared/event'
const AutoRefreshDropdown: FC<TimeContextProps> = ({context, update}) => {
const {refresh} = context
const updateRefresh = (interval: number) => {
const status =
interval === 0 ? AutoRefreshStatus.Paused : AutoRefreshStatus.Active
const updateRefresh = useCallback(
(interval: number) => {
const status =
interval === 0 ? AutoRefreshStatus.Paused : AutoRefreshStatus.Active
event('Auto Refresh Updated', {
interval: '' + interval,
})
event('Auto Refresh Updated', {
interval: '' + interval,
})
update({
refresh: {
status,
interval,
},
} as TimeBlock)
}
update({
refresh: {
status,
interval,
},
} as TimeBlock)
},
[update]
)
return useMemo(
() => (
return useMemo(() => {
return (
<StatelessAutoRefreshDropdown
selected={refresh}
onChoose={updateRefresh}
showManualRefresh={false}
/>
),
[refresh]
)
)
}, [refresh, updateRefresh])
}
export default AutoRefreshDropdown

View File

@ -28,7 +28,7 @@ const Buttons: FC = () => {
(data: TimeBlock) => {
updateTimeContext(id, data)
},
[id]
[id, updateTimeContext]
)
if (!timeContext.hasOwnProperty(id)) {

View File

@ -25,12 +25,13 @@ export const Submit: FC = () => {
const {timeContext} = useContext(TimeContext)
const [isLoading, setLoading] = useState(RemoteDataState.NotStarted)
const time = timeContext[id]
const tr = !!time && time.range
useEffect(() => {
submit()
}, [!!time && time.range])
}, [tr]) // eslint-disable-line react-hooks/exhaustive-deps
const submit = () => {
const submit = async () => {
event('Notebook Submit Button Clicked')
setLoading(RemoteDataState.Loading)
@ -90,7 +91,9 @@ export const Submit: FC = () => {
})
.catch(e => {
queryStruct.instances.forEach(index => {
updateMeta(index, {loading: RemoteDataState.Error} as PipeMeta)
updateMeta(index, {
loading: RemoteDataState.Error,
} as PipeMeta)
updateResult(index, {
error: e.message,
} as BothResults)

View File

@ -1,4 +1,4 @@
import React, {FC, useMemo} from 'react'
import React, {FC, useMemo, useCallback} from 'react'
import {default as StatelessTimeRangeDropdown} from 'src/shared/components/TimeRangeDropdown'
import {TimeContextProps} from 'src/notebooks/components/header/Buttons'
import {TimeBlock} from 'src/notebooks/context/time'
@ -9,17 +9,20 @@ import {event} from 'src/notebooks/shared/event'
const TimeRangeDropdown: FC<TimeContextProps> = ({context, update}) => {
const {range} = context
const updateRange = range => {
event('Time Range Updated', {
type: range.type,
upper: range.upper as string,
lower: range.lower,
})
const updateRange = useCallback(
range => {
event('Time Range Updated', {
type: range.type,
upper: range.upper as string,
lower: range.lower,
})
update({
range,
} as TimeBlock)
}
update({
range,
} as TimeBlock)
},
[update]
)
return useMemo(() => {
return (
@ -28,7 +31,7 @@ const TimeRangeDropdown: FC<TimeContextProps> = ({context, update}) => {
onSetTimeRange={updateRange}
/>
)
}, [range])
}, [range, updateRange])
}
export default TimeRangeDropdown

View File

@ -10,10 +10,11 @@ import {event} from 'src/notebooks/shared/event'
interface Props {
onClick?: () => void
direction: 'up' | 'down'
active: boolean
}
const MovePanelUpButton: FC<Props> = ({onClick, direction}) => {
const status = onClick ? ComponentStatus.Default : ComponentStatus.Disabled
const MovePanelUpButton: FC<Props> = ({onClick, direction, active}) => {
const status = active ? ComponentStatus.Default : ComponentStatus.Disabled
const icon = direction === 'up' ? IconFont.CaretUp : IconFont.CaretDown
const handleClick = (e: MouseEvent<HTMLButtonElement>): void => {

View File

@ -44,15 +44,19 @@ const NotebookPanelHeader: FC<HeaderProps> = ({index, controls}) => {
const canBeMovedUp = index > 0
const canBeMovedDown = index < pipes.length - 1
const moveUp = useCallback(
canBeMovedUp ? () => movePipe(index, index - 1) : null,
[index, pipes]
)
const moveDown = useCallback(
canBeMovedDown ? () => movePipe(index, index + 1) : null,
[index, pipes]
)
const remove = useCallback(() => removePipe(index), [index, pipes])
const moveUp = useCallback(() => {
if (canBeMovedUp) {
movePipe(index, index - 1)
}
}, [index, canBeMovedUp, movePipe])
const moveDown = useCallback(() => {
if (canBeMovedDown) {
movePipe(index, index + 1)
}
}, [index, canBeMovedDown, movePipe])
const remove = useCallback(() => removePipe(index), [removePipe, index])
return (
<div className="notebook-panel--header">
@ -71,8 +75,16 @@ const NotebookPanelHeader: FC<HeaderProps> = ({index, controls}) => {
justifyContent={JustifyContent.FlexEnd}
>
{controls}
<MovePanelButton direction="up" onClick={moveUp} />
<MovePanelButton direction="down" onClick={moveDown} />
<MovePanelButton
direction="up"
onClick={moveUp}
active={canBeMovedUp}
/>
<MovePanelButton
direction="down"
onClick={moveDown}
active={canBeMovedDown}
/>
<PanelVisibilityToggle index={index} />
<RemovePanelButton onRemove={remove} />
</FlexBox>
@ -88,8 +100,10 @@ const NotebookPanel: FC<Props> = ({index, children, controls}) => {
const isFocused = meta[index].focus
useEffect(() => {
updateMeta(index, {panelRef} as PipeMeta)
}, [])
updateMeta(index, {
panelRef,
} as PipeMeta)
}, []) // eslint-disable-line react-hooks/exhaustive-deps
const panelClassName = classnames('notebook-panel', {
[`notebook-panel__visible`]: isVisible,
@ -99,9 +113,11 @@ const NotebookPanel: FC<Props> = ({index, children, controls}) => {
const updatePanelFocus = useCallback(
(focus: boolean): void => {
updateMeta(index, {focus} as PipeMeta)
updateMeta(index, {
focus,
} as PipeMeta)
},
[index, meta]
[index, meta] // eslint-disable-line react-hooks/exhaustive-deps
)
const handleClick = (e: MouseEvent<HTMLDivElement>): void => {

View File

@ -48,7 +48,7 @@ export const BucketProvider: FC<Props> = React.memo(
}
lockAndLoad(getBuckets)
}, [loading])
}, [loading, getBuckets])
return (
<BucketContext.Provider

View File

@ -79,9 +79,9 @@ export const NotebookProvider: FC = ({children}) => {
const [meta, setMeta] = useState(DEFAULT_CONTEXT.meta)
const [results, setResults] = useState(DEFAULT_CONTEXT.results)
const _setPipes = useCallback(setPipes, [id])
const _setMeta = useCallback(setMeta, [id])
const _setResults = useCallback(setResults, [id])
const _setPipes = useCallback(setPipes, [id, setPipes])
const _setMeta = useCallback(setMeta, [id, setMeta])
const _setResults = useCallback(setResults, [id, setResults])
const addPipe = useCallback(
(pipe: PipeData, insertAtIndex?: number) => {
@ -141,7 +141,7 @@ export const NotebookProvider: FC = ({children}) => {
_setPipes(add(pipe))
}
},
[id, pipes, meta, results]
[pipes, meta, results, _setPipes, _setMeta, _setResults]
)
const updatePipe = useCallback(
@ -154,7 +154,7 @@ export const NotebookProvider: FC = ({children}) => {
return pipes.slice()
})
},
[id, pipes]
[_setPipes]
)
const updateMeta = useCallback(
@ -167,7 +167,7 @@ export const NotebookProvider: FC = ({children}) => {
return pipes.slice()
})
},
[id, meta]
[_setMeta]
)
const updateResult = useCallback(
@ -179,7 +179,7 @@ export const NotebookProvider: FC = ({children}) => {
return pipes.slice()
})
},
[id, results]
[_setResults]
)
const movePipe = useCallback(
@ -201,7 +201,7 @@ export const NotebookProvider: FC = ({children}) => {
_setMeta(move)
_setResults(move)
},
[id]
[_setPipes, _setResults, _setMeta]
)
const removePipe = useCallback(
@ -214,7 +214,7 @@ export const NotebookProvider: FC = ({children}) => {
_setMeta(remove)
_setResults(remove)
},
[id]
[_setPipes, _setMeta, _setResults]
)
return (

View File

@ -1,5 +1,5 @@
// Libraries
import React, {FC, useEffect, useContext} from 'react'
import React, {FC, useEffect, useContext, useCallback} from 'react'
// Components
import {
@ -24,9 +24,12 @@ const BucketSelector: FC<Props> = ({onUpdate, data}) => {
const selectedBucketName = data.bucketName
const {buckets, loading} = useContext(BucketContext)
const updateBucket = (updatedBucket: Bucket): void => {
onUpdate({bucketName: updatedBucket.name})
}
const updateBucket = useCallback(
(updatedBucket: Bucket): void => {
onUpdate({bucketName: updatedBucket.name})
},
[onUpdate]
)
useEffect(() => {
// selectedBucketName will only evaluate false on the initial render
@ -34,7 +37,7 @@ const BucketSelector: FC<Props> = ({onUpdate, data}) => {
if (!!buckets.length && !selectedBucketName) {
updateBucket(buckets[0])
}
}, [buckets])
}, [buckets, selectedBucketName, updateBucket])
let body

View File

@ -14,8 +14,8 @@ import BucketProvider from 'src/notebooks/context/buckets'
import 'src/notebooks/pipes/Query/style.scss'
const DataSource: FC<PipeProp> = ({data, onUpdate, Context}) => {
return useMemo(
() => (
return useMemo(() => {
return (
<BucketProvider>
<Context>
<FlexBox
@ -28,9 +28,8 @@ const DataSource: FC<PipeProp> = ({data, onUpdate, Context}) => {
</FlexBox>
</Context>
</BucketProvider>
),
[data.bucketName, data.timeStart, data.timeStop]
)
)
}, [data, onUpdate])
}
export default DataSource

View File

@ -37,7 +37,7 @@ const Query: FC<PipeProp> = ({data, onUpdate, Context, results}) => {
<Results results={results} onUpdate={onUpdate} data={data} />
</Context>
),
[query.text, results, data.panelVisibility, data.panelHeight]
[query.text, results, data.panelVisibility, data.panelHeight] // eslint-disable-line react-hooks/exhaustive-deps
)
}

View File

@ -7,22 +7,21 @@ interface Props {
const Embedded: FC<Props> = ({uri, visible}) => {
const parts = uri.split(':')
if (!visible) {
return null
}
const p1 = parts[1]
const p2 = parts[2]
return useMemo(
() => (
<iframe
src={`https://open.spotify.com/embed/${parts[1]}/${parts[2]}`}
width="600"
height="80"
frameBorder="0"
allow="encrypted-media"
/>
),
[uri]
() =>
visible && (
<iframe
src={`https://open.spotify.com/embed/${p1}/${p2}`}
width="600"
height="80"
frameBorder="0"
allow="encrypted-media"
/>
),
[visible, p1, p2]
)
}

View File

@ -1,5 +1,5 @@
import React, {FC, useEffect, useState} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {getDashboards} from 'src/dashboards/actions/thunks'
import {
createCellWithView,
@ -66,16 +66,16 @@ const DashboardList: FC<Props> = ({
properties,
onClose,
dashboards,
loadDashboards,
createView,
createViewAndDashboard,
}) => {
const dispatch = useDispatch()
const [selectedDashboard, setSelectedDashboard] = useState(null)
const [newName, setNewName] = useState(DEFAULT_DASHBOARD_NAME)
useEffect(() => {
loadDashboards()
}, [])
dispatch(getDashboards())
}, [dispatch])
const isEditingName =
selectedDashboard && selectedDashboard.id === DashboardTemplate.id
@ -212,7 +212,6 @@ const mstp = (state: AppState) => {
}
const mdtp = {
loadDashboards: getDashboards,
createView: createCellWithView,
createViewAndDashboard: createDashboardWithView,
notify: notifyAction,

View File

@ -1,5 +1,12 @@
// Libraries
import React, {FC, useRef, useEffect, ReactNode, useState} from 'react'
import React, {
FC,
useRef,
useEffect,
ReactNode,
useState,
useCallback,
} from 'react'
import classnames from 'classnames'
// Components
@ -60,39 +67,29 @@ const Resizer: FC<Props> = ({
[`panel-resizer--body__${visibility}`]: resizingEnabled && visibility,
})
const updateResultsStyle = (): void => {
const updateResultsStyle = useCallback(() => {
if (bodyRef.current && resizingEnabled && visibility === 'visible') {
bodyRef.current.setAttribute('style', `height: ${size}px`)
} else {
bodyRef.current.setAttribute('style', '')
}
}
}, [resizingEnabled, size, visibility])
const handleUpdateVisibility = (panelVisibility: Visibility): void => {
onUpdate({panelVisibility})
}
const handleUpdateHeight = (panelHeight: number): void => {
onUpdate({panelHeight})
}
const handleUpdateHeight = useCallback(
(panelHeight: number): void => {
onUpdate({panelHeight})
},
[onUpdate]
)
// Ensure results renders with proper height on initial render
// Ensure styles update when state & props update
useEffect(() => {
updateResultsStyle()
}, [])
// Update results height when associated props change
useEffect(() => {
updateResultsStyle()
}, [size, visibility, resizingEnabled])
// Update local height when context height changes
// so long as it is a different value
useEffect(() => {
if (height !== size) {
updateSize(height)
}
}, [height])
}, [updateResultsStyle])
// Handle changes in drag state
useEffect(() => {
@ -110,7 +107,7 @@ const Resizer: FC<Props> = ({
)
handleUpdateHeight(size)
}
}, [isDragging])
}, [isDragging, size, handleUpdateHeight])
const handleMouseMove = (e: MouseEvent): void => {
if (!bodyRef.current) {

View File

@ -48,7 +48,7 @@ const RuleMessage: FC<Props> = ({endpoints, rule}) => {
if (!rule.endpointID && endpoints.length) {
onSelectEndpoint(endpoints[0].id)
}
}, [])
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return (
<Grid.Row>

View File

@ -1,7 +1,7 @@
// Libraries
import React, {FC, Component, ComponentClass, useEffect} from 'react'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {connect, ConnectedProps} from 'react-redux'
import {useDispatch} from 'react-redux'
import {OverlayID} from 'src/overlays/reducers/overlays'
// Actions
@ -20,48 +20,36 @@ interface OwnProps {
onClose: OverlayDismissalWithRoute
}
type ReduxProps = ConnectedProps<typeof connector>
type OverlayHandlerProps = OwnProps & ReduxProps & RouteComponentProps
type OverlayHandlerProps = OwnProps & RouteComponentProps
const OverlayHandler: FC<OverlayHandlerProps> = props => {
const {
overlayID,
onShowOverlay,
onClose,
match,
history,
onDismissOverlay,
} = props
const {overlayID, onClose, match, history} = props
const dispatch = useDispatch()
useEffect(() => {
const closer = () => {
onClose(history, match.params)
}
onShowOverlay(overlayID, match.params, closer)
dispatch(showOverlay(overlayID, match.params, closer))
return () => onDismissOverlay()
}, [overlayID])
return () => dispatch(dismissOverlay())
}, [overlayID]) // eslint-disable-line react-hooks/exhaustive-deps
return null
}
const mdtp = {
onShowOverlay: showOverlay,
onDismissOverlay: dismissOverlay,
}
const routedComponent = withRouter(OverlayHandler)
const connector = connect(null, mdtp)
const connectedComponent = connector(withRouter(OverlayHandler))
export default connectedComponent
export default routedComponent
interface RouteOverlayProps {
overlayID: OverlayID
}
export function RouteOverlay<P>(
WrappedComponent: typeof connectedComponent,
WrappedComponent: typeof routedComponent,
overlayID: string,
onClose?: OverlayDismissalWithRoute
): ComponentClass<P> {

View File

@ -83,7 +83,7 @@ const DeleteDataForm: FC<Props> = ({
if (filters.every(filter => filter.key !== '' && filter.value !== '')) {
handleDeleteDataPreview()
}
}, [filters])
}, [filters]) // eslint-disable-line react-hooks/exhaustive-deps
const formatPredicatesForPreview = (predicates: Filter[]) => {
let result = ''

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {Overlay, SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
@ -26,19 +26,19 @@ const DeleteDataOverlay: FunctionComponent<Props> = ({
match: {
params: {orgID, bucketID},
},
resetPredicateState,
setBucketAndKeys,
}) => {
const dispatch = useDispatch()
const bucket = buckets.find(bucket => bucket.id === bucketID)
const bucketName = bucket?.name
useEffect(() => {
if (bucket) {
setBucketAndKeys(bucket.name)
if (bucketName) {
dispatch(setBucketAndKeys(bucketName))
}
}, [])
}, [bucketName, dispatch])
const handleDismiss = () => {
resetPredicateState()
dispatch(resetPredicateState())
history.push(`/orgs/${orgID}/load-data/buckets/`)
}
@ -65,11 +65,6 @@ const mstp = (state: AppState) => {
}
}
const mdtp = {
resetPredicateState,
setBucketAndKeys,
}
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(withRouter(DeleteDataOverlay))

View File

@ -41,7 +41,7 @@ const DurationInput: FC<Props> = ({
if (value != inputValue) {
setInputValue(value)
}
}, [value])
}, [value, inputValue])
const handleClickSuggestion = (suggestion: string) => {
setInputValue(suggestion)

View File

@ -136,7 +136,7 @@ const ThresholdsSettings: FunctionComponent<Props> = ({
if (state.isDirty && state.isValid) {
onSetThresholds(state.thresholds)
}
}, [state])
}, [state, onSetThresholds])
return (
<FlexBox

View File

@ -1,6 +1,6 @@
// Libraries
import React, {useEffect, FC} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {Switch, Route} from 'react-router-dom'
import GetOrganizations from 'src/shared/containers/GetOrganizations'
@ -11,7 +11,7 @@ import {SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
import {RemoteDataState, AppState} from 'src/types'
// Actions
import {getFlags as getFlagsAction} from 'src/shared/actions/flags'
import {getFlags} from 'src/shared/actions/flags'
// Utils
import {activeFlags} from 'src/shared/selectors/flags'
@ -20,12 +20,13 @@ import {updateReportingContext} from 'src/cloud/utils/reporting'
type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps
const GetFlags: FC<Props> = ({status, getFlags, flags}) => {
const GetFlags: FC<Props> = ({status, flags}) => {
const dispatch = useDispatch()
useEffect(() => {
if (status === RemoteDataState.NotStarted) {
getFlags()
dispatch(getFlags())
}
}, [])
}, [dispatch, status])
useEffect(() => {
updateReportingContext(
@ -46,15 +47,11 @@ const GetFlags: FC<Props> = ({status, getFlags, flags}) => {
)
}
const mdtp = {
getFlags: getFlagsAction,
}
const mstp = (state: AppState) => ({
flags: activeFlags(state),
status: state.flags.status || RemoteDataState.NotStarted,
})
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(GetFlags)

View File

@ -1,6 +1,6 @@
// Libraries
import React, {useEffect, FunctionComponent} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {Route, Switch} from 'react-router-dom'
// Components
@ -12,21 +12,19 @@ import App from 'src/App'
import {RemoteDataState, AppState} from 'src/types'
// Actions
import {getOrganizations as getOrganizationsAction} from 'src/organizations/actions/thunks'
import {getOrganizations} from 'src/organizations/actions/thunks'
import RouteToOrg from './RouteToOrg'
type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps
const GetOrganizations: FunctionComponent<Props> = ({
status,
getOrganizations,
}) => {
const GetOrganizations: FunctionComponent<Props> = ({status}) => {
const dispatch = useDispatch()
useEffect(() => {
if (status === RemoteDataState.NotStarted) {
getOrganizations()
dispatch(getOrganizations())
}
}, [])
}, [dispatch, status])
return (
<SpinnerContainer loading={status} spinnerComponent={<TechnoSpinner />}>
@ -39,14 +37,10 @@ const GetOrganizations: FunctionComponent<Props> = ({
)
}
const mdtp = {
getOrganizations: getOrganizationsAction,
}
const mstp = ({resources}: AppState) => ({
status: resources.orgs.status,
})
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(GetOrganizations)

View File

@ -1,6 +1,6 @@
// Libraries
import React, {useEffect, useState, FC} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
import {Route, Switch} from 'react-router-dom'
// Components
@ -35,7 +35,7 @@ import {AppState, Organization, ResourceType} from 'src/types'
import {CLOUD} from 'src/shared/constants'
// Actions
import {setOrg as setOrgAction} from 'src/organizations/actions/creators'
import {setOrg} from 'src/organizations/actions/creators'
// Utils
import {updateReportingContext} from 'src/cloud/utils/reporting'
@ -65,15 +65,16 @@ const SetOrg: FC<Props> = ({
},
orgs,
history,
setOrg,
}) => {
const [loading, setLoading] = useState(RemoteDataState.Loading)
const dispatch = useDispatch()
const foundOrg = orgs.find(o => o.id === orgID)
const firstOrgID = orgs[0]?.id
useEffect(() => {
// does orgID from url match any orgs that exist
const foundOrg = orgs.find(o => o.id === orgID)
if (foundOrg) {
setOrg(foundOrg)
dispatch(setOrg(foundOrg))
updateReportingContext({orgID: orgID})
setLoading(RemoteDataState.Done)
return
@ -86,8 +87,8 @@ const SetOrg: FC<Props> = ({
}
// else default to first org
history.push(`/orgs/${orgs[0].id}`)
}, [orgID, orgs.length])
history.push(`/orgs/${firstOrgID}`)
}, [orgID, firstOrgID, foundOrg, dispatch, history, orgs.length])
const orgPath = '/orgs/:orgID'
@ -179,16 +180,12 @@ const SetOrg: FC<Props> = ({
)
}
const mdtp = {
setOrg: setOrgAction,
}
const mstp = (state: AppState) => {
const orgs = getAll<Organization>(state, ResourceType.Orgs)
return {orgs}
}
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(SetOrg)

View File

@ -24,7 +24,7 @@ export const useMountedEffect = (
}
return effect()
}, inputs)
}, inputs) // eslint-disable-line react-hooks/exhaustive-deps
}
export const useMountedLayoutEffect = (
@ -41,5 +41,5 @@ export const useMountedLayoutEffect = (
}
return effect()
}, inputs)
}, inputs) // eslint-disable-line react-hooks/exhaustive-deps
}

View File

@ -36,7 +36,7 @@ export const useOneWayReducer = <R extends Reducer<any, any>>(
return reducer(state, action)
},
[]
[reducer]
)
const [reducerState, dispatch] = useReducer(wrappedReducer, defaultState)

View File

@ -47,7 +47,7 @@ export const useVisXDomainSettings = (
}
return getValidRange(data, timeRange)
}, [storedDomain, data])
}, [storedDomain, data]) // eslint-disable-line react-hooks/exhaustive-deps
const [domain, setDomain] = useOneWayState(initialDomain)
const resetDomain = () => setDomain(initialDomain)
@ -95,7 +95,7 @@ export const useVisYDomainSettings = (
return getRemainingRange(data, timeRange, storedDomain)
}
return storedDomain
}, [storedDomain, data])
}, [storedDomain, data]) // eslint-disable-line react-hooks/exhaustive-deps
const [domain, setDomain] = useOneWayState(initialDomain)
const resetDomain = () => setDomain(initialDomain)

View File

@ -21,10 +21,6 @@ interface Props {
}
const CommunityTemplateListGroup: FC<Props> = ({title, count, children}) => {
if (!React.Children.count(children)) {
return null
}
const [mode, setMode] = useState<'expanded' | 'collapsed'>('collapsed')
const groupClassName = classnames('community-templates--list-group', {
[`community-templates--list-group__${mode}`]: mode,
@ -38,6 +34,10 @@ const CommunityTemplateListGroup: FC<Props> = ({title, count, children}) => {
}
}
if (!React.Children.count(children)) {
return null
}
return (
<div className={groupClassName}>
<div

View File

@ -7,7 +7,7 @@ import React, {
useRef,
useReducer,
} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
// Components
import {
@ -26,10 +26,7 @@ import {
} from 'src/cloud/utils/limits'
// Actions
import {
checkBucketLimits as checkBucketLimitsAction,
LimitStatus,
} from 'src/cloud/actions/limits'
import {checkBucketLimits, LimitStatus} from 'src/cloud/actions/limits'
import {createBucket} from 'src/buckets/actions/thunks'
// Types
@ -53,8 +50,8 @@ const SelectorListCreateBucket: FC<Props> = ({
createBucket,
isRetentionLimitEnforced,
limitStatus,
checkBucketLimits,
}) => {
const reduxDispatch = useDispatch()
const triggerRef = useRef<HTMLButtonElement>(null)
const [state, dispatch] = useReducer(
createBucketReducer,
@ -63,8 +60,8 @@ const SelectorListCreateBucket: FC<Props> = ({
useEffect(() => {
// Check bucket limits when component mounts
checkBucketLimits()
}, [])
reduxDispatch(checkBucketLimits())
}, [reduxDispatch])
const limitExceeded = limitStatus === LimitStatus.EXCEEDED
@ -172,7 +169,6 @@ const mstp = (state: AppState) => {
const mdtp = {
createBucket,
checkBucketLimits: checkBucketLimitsAction,
}
const connector = connect(mstp, mdtp)

View File

@ -1,6 +1,6 @@
// Libraries
import React, {useState, useEffect, FunctionComponent} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {connect, ConnectedProps, useDispatch} from 'react-redux'
// Components
import FluxToolbarSearch from 'src/timeMachine/components/FluxToolbarSearch'
@ -30,14 +30,14 @@ type Props = OwnProps & ReduxProps
const VariableToolbar: FunctionComponent<Props> = ({
variables,
onClickVariable,
hydrateVariables,
}) => {
const dispatch = useDispatch()
const [searchTerm, setSearchTerm] = useState('')
const filteredVariables = variables.filter(v => v.name.includes(searchTerm))
useEffect(() => {
hydrateVariables()
}, [])
dispatch(hydrateVariables())
}, [dispatch])
let content: JSX.Element | JSX.Element[] = (
<EmptyState size={ComponentSize.ExtraSmall}>
@ -72,10 +72,6 @@ const mstp = (state: AppState) => {
return {variables: sortVariablesByName(variables)}
}
const mdtp = {
hydrateVariables: hydrateVariables,
}
const connector = connect(mstp, mdtp)
const connector = connect(mstp)
export default connector(VariableToolbar)

View File

@ -4345,6 +4345,11 @@ eslint-plugin-jest@^23.0.2:
dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0"
eslint-plugin-react-hooks@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.5.tgz#4879003aa38e5d05d0312175beb6e4a1f617bfcf"
integrity sha512-3YLSjoArsE2rUwL8li4Yxx1SUg3DQWp+78N3bcJQGWVZckcp+yeQGsap/MSq05+thJk57o+Ww4PtZukXGL02TQ==
eslint-plugin-react@^7.16.0:
version "7.16.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09"