2
\$\begingroup\$

This is a select-all checkbox (and checkboxes that it controls), that's usually found in tables/lists.

Two things I'm concerned about:

  1. I'm returning elements from a React Hook - is that OK to do? As opposed to create a Component (probably a render prop)

  2. I'm calculating master.indeterminate in a useEffect, as opposed to doing it when the checkboxes are clicked.

I'm planning on using it in a table that will have 1000s of entries, filtered/paginated upto 100.

I realize #2 could be a performance issue since I'm essentially checking all checkboxes to determine whether to set master.indeterminate, but since the table will be filtered I want this to be "reactive" as well.

Any other issues I might be overlooking?

Fiddle

const h = (...args) => React.createElement(...args)

function useSelectAll(existingStateOrSize = []) {
  const [state, update] = React.useState(existingStateOrSize)
  const master = React.useRef(null)
  const children = React.useRef(state.map(() => null))

  function setIndeterminate () {
    /* Calculate master.indeterminate */
    let allChecked
    for (const child of children.current) {
      if (!child) continue
      const checked = Boolean(child.checked)
      if (allChecked === undefined) {
        allChecked = checked
      } else if (allChecked !== checked) {
        allChecked = null
        break
      }
    }
    if (typeof allChecked === 'boolean') {
      master.current.checked = allChecked
      master.current.indeterminate = false
    } else {
      master.current.indeterminate = true
    }
  }

  function onChangeMaster() {
    children.current.forEach(child => child.checked = master.current.checked)
  }

  function onChangeChild(e) {
    const i = children.current.indexOf(e.target)
    const newState = Array.from(state)
    newState[i] = e.target.checked
    update(newState)
  }

  React.useEffect(setIndeterminate)

  return [
    state,
    h('input', { type: 'checkbox', ref: master, onChange: onChangeMaster }),
    state.map((x, i) => h('input', { type: 'checkbox', ref: r => children.current[i] = r, checked: existingStateOrSize[i], onChange: onChangeChild }))
  ]
}

function Table() {
  const [state, master, children] = useSelectAll([null,null,null])
  return h('table', {}, [
    h('tr', {}, h('th', {}, master), h('th', {}, 'Select all')),
    h('tr', {}, h('td', {}, children[0]), h('td', {}, 'First')),
    h('tr', {}, h('td', {}, children[1]), h('td', {}, 'Second')),
    h('tr', {}, h('td', {}, children[2]), h('td', {}, 'Third')),
  ])
}

ReactDOM.render(h(Table), document.getElementById('container'))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.