Repository: h2oai/wave
File: ui/src/table.tsx (TypeScript)

Code before:
          if (m.pagination && m.events?.includes('filter')) {
            wave.emit(m.name, 'filter', newFilters)
            setCurrentPage(1)
          }
          else {
            filter(newFilters)
            search()
            setGroups(groups => {
              if (groups) initGroups()
              return groups
            })
          }
          return newFilters
        })
      }, [m.pagination, m.events, m.name, filter, search, initGroups])

    React.useEffect(() => {
      wave.args[m.name] = []
      if (isMultiple && m.values) {
        m.values.forEach(v => selection.setKeySelected(v, true, false))
        wave.args[m.name] = m.values
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    React.useEffect(() => {
      // Skip for initial render.
      if (didMountRef.current) {
        setFilteredItems(items)
        reset()
      } else didMountRef.current = true
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [items])

    React.useEffect(() => {
      if (m.groups) {
        expandedRefs.current = m.groups?.reduce((acc, { label, collapsed = true }) => {
          if (!collapsed) acc[label] = false
          return acc
        }, {} as { [key: S]: B })
        initGroups()
      }
    }, [initGroups, m.groups])

    const dataTableProps: DataTable = React.useMemo(() => ({
      model: m,
      onFilterChange,
      items,
      filteredItems,
      selectedFilters,
      groups,

Review comment (refactor):
This seems like a common thing we use and will use in the future due to React being what it is. Wdyt about extracting this into a separate custom hook? Something like:

```tsx
import React from 'react'

const useUpdateOnlyEffect = (func: (...args: any) => any, deps: any[]) => {
  const didMount = React.useRef(false)
  React.useEffect(() => { didMount.current ? func() : didMount.current = true }, deps)
}

export default useUpdateOnlyEffect
```

Code after:
      setFiltersInBulk = React.useCallback((colKey: S, filters: S[]) => {
        setSelectedFilters(selectedFilters => {
          const newFilters = {
            ...selectedFilters,
            [colKey]: filters
          }
          if (m.pagination && m.events?.includes('filter')) {
            wave.emit(m.name, 'filter', newFilters)
            setCurrentPage(1)
          }
          else {
            filter(newFilters)
            search()
            setGroups(groups => {
              if (groups) initGroups()
              return groups
            })
          }
          return newFilters
        })
      }, [m.pagination, m.events, m.name, filter, search, initGroups])

    React.useEffect(() => {
      wave.args[m.name] = []
      if (isMultiple && m.values) {
        m.values.forEach(v => selection.setKeySelected(v, true, false))
        wave.args[m.name] = m.values
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    useUpdateOnlyEffect(() => {
      setFilteredItems(items)
      reset()
    }, [items])

    React.useEffect(() => {
      if (m.groups) {
        expandedRefs.current = m.groups?.reduce((acc, { label, collapsed = true }) => {
          if (!collapsed) acc[label] = false
          return acc
        }, {} as { [key: S]: B })
        initGroups()
      }
    }, [initGroups, m.groups])

    const dataTableProps: DataTable = React.useMemo(() => ({
      model: m,
      onFilterChange,
      items,
      filteredItems,