Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add debounced data-element search #88

Draft
wants to merge 3 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import i18n from '@dhis2/d2-i18n'
import { InputField } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react'
import useDebounce from './use-debounce.js'
import { useDebouncedValue } from '../../shared/index.js'

export default function DebouncedSearchInput({ onChange, initialValue }) {
const [value, setValue] = useState(initialValue)
const debouncedValue = useDebounce(value, 200)
const debouncedValue = useDebouncedValue(value, 200)

useEffect(() => {
onChange(debouncedValue)
Expand Down
13 changes: 0 additions & 13 deletions src/context-selection/org-unit-selector-bar-item/use-debounce.js

This file was deleted.

5 changes: 3 additions & 2 deletions src/data-workspace/entry-form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import { useDebounce } from '../shared/index.js'
import { FORM_TYPES } from './constants.js'
import { CustomForm } from './custom-form/index.js'
import { DefaultForm } from './default-form.js'
Expand All @@ -17,15 +18,15 @@ export const EntryForm = ({ dataSet }) => {
const [globalFilterText, setGlobalFilterText] = React.useState('')
const formType = dataSet.formType
const Component = formTypeToComponent[formType]
const debouncedSetGlobalFilterText = useDebounce(setGlobalFilterText, 200)

useCloseRightHandPanelOnSelectionChange()

return (
<>
{formType !== FORM_TYPES.CUSTOM && (
<FilterField
value={globalFilterText}
setFilterText={setGlobalFilterText}
onFilterChange={debouncedSetGlobalFilterText}
formType={formType}
/>
)}
Expand Down
18 changes: 12 additions & 6 deletions src/data-workspace/filter-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import i18n from '@dhis2/d2-i18n'
import { Button, InputField } from '@dhis2/ui'
import classNames from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'
import React, { useState } from 'react'
import { FORM_TYPES } from './constants.js'
import styles from './entry-form.module.css'

export default function FilterField({ value, setFilterText, formType }) {
export default function FilterField({ onFilterChange, formType }) {
const [filterText, setFilterText] = useState('')
const wrapperClasses = classNames(styles.filterWrapper, 'hide-for-print')

const onChange = ({ value }) => {
setFilterText(value)
onFilterChange(value)
}

return (
<div className={wrapperClasses}>
<InputField
Expand All @@ -19,8 +26,8 @@ export default function FilterField({ value, setFilterText, formType }) {
? i18n.t('Filter fields in all sections')
: i18n.t('Filter fields')
}
value={value}
onChange={({ value }) => setFilterText(value)}
value={filterText}
onChange={onChange}
/>
<Button
secondary
Expand All @@ -36,6 +43,5 @@ export default function FilterField({ value, setFilterText, formType }) {

FilterField.propTypes = {
formType: PropTypes.string,
setFilterText: PropTypes.func,
value: PropTypes.string,
onFilterChange: PropTypes.func,
}
33 changes: 33 additions & 0 deletions src/data-workspace/section-form/section-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import i18n from '@dhis2/d2-i18n'
import { colors, IconFilter16 } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import styles from './section.module.css'

export const SectionFilter = ({ id, onFilterChange }) => {
const [filterText, setFilterText] = useState('')
const onChange = ({ target }) => {
setFilterText(target.value)
onFilterChange(filterText)
}

return (
<label htmlFor={id} className={styles.filterWrapper}>
<IconFilter16 color={colors.grey600} />
<input
name={id}
id={id}
type="text"
placeholder={i18n.t('Type here to filter in this section')}
value={filterText}
onChange={onChange}
className={styles.filterInput}
/>
</label>
)
}

SectionFilter.propTypes = {
id: PropTypes.string,
onFilterChange: PropTypes.func,
}
36 changes: 8 additions & 28 deletions src/data-workspace/section-form/section.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import i18n from '@dhis2/d2-i18n'
import {
colors,
IconFilter16,
Table,
TableCellHead,
TableHead,
TableRowHead,
} from '@dhis2/ui'
import { Table, TableCellHead, TableHead, TableRowHead } from '@dhis2/ui'
import classNames from 'classnames'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import { useMetadata, selectors } from '../../metadata/index.js'
import { useDebounce } from '../../shared/index.js'
import { CategoryComboTable } from '../category-combo-table/index.js'
import { getFieldId } from '../get-field-id.js'
import { SectionFilter } from './section-filter.js'
import styles from './section.module.css'

export const SectionFormSection = ({
Expand All @@ -22,6 +16,7 @@ export const SectionFormSection = ({
}) => {
// Could potentially build table via props instead of rendering children
const [filterText, setFilterText] = useState('')
const debouncedSetFilterText = useDebounce(setFilterText, 200)
const { data } = useMetadata()

if (!data) {
Expand Down Expand Up @@ -75,25 +70,10 @@ export const SectionFormSection = ({
</TableRowHead>
<TableRowHead>
<TableCellHead colSpan="100%" className={headerCellStyles}>
<label
htmlFor={filterInputId}
className={styles.filterWrapper}
>
<IconFilter16 color={colors.grey600} />
<input
name={filterInputId}
id={filterInputId}
type="text"
placeholder={i18n.t(
'Type here to filter in this section'
)}
value={filterText}
onChange={({ target }) =>
setFilterText(target.value)
}
className={styles.filterInput}
/>
</label>
<SectionFilter
id={filterInputId}
onFilterChange={debouncedSetFilterText}
/>
</TableCellHead>
</TableRowHead>
</TableHead>
Expand Down
1 change: 1 addition & 0 deletions src/shared/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './fixed-periods/index.js'
export * from './period/index.js'
export * from './sidebar/index.js'
export * from './utils.js'
export * from './use-debounce.js'
29 changes: 29 additions & 0 deletions src/shared/use-debounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useCallback, useEffect, useRef, useState } from 'react'

/** Copied from https://usehooks.com/useDebounce/ */
export function useDebouncedValue(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value)

useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay)
return () => clearTimeout(handler)
}, [value, delay])

return debouncedValue
}

/*
* If this component ever causes issues,
* consider using https://github.com/xnimorz/use-debounce
*/
export function useDebounce(callback, delay) {
const timeoutIdRef = useRef(null)

return useCallback(
(...args) => {
clearTimeout(timeoutIdRef.current)
timeoutIdRef.current = setTimeout(() => callback(...args), delay)
},
[callback, delay]
)
}