Nested Checkbox Coding Challenge

I had an interview challenge shared with me that's used at a large fintech company. This is my solution.

Requirements

Implement a nested checkbox component that satisfies the following interface:

type CheckboxOption = {
  [key: string]: boolean | CheckboxOption;
};

const NestedCheckbox: ({
  value,
  onChange
}: {
  value: CheckboxOption;
  onChange: (value: CheckboxOption) => void;
}) => React.ReactNode;

The following input should produce the following output:

const App = () => {
  const [value, onChange] = useState({
    foo: true,
    bar: false,
    group1: {
      level2: true,
      level2option2: false,
      nested: {
        option: true
      }
    }
  });

  return <NestedCheckbox value={value} onChange={onChange} />;
};

The requirements for the implementation are:

  • The non-leaf checkboxes (those corresponding to object values) are checked only if all of their child leaf checkboxes are checked
  • There is no state maintained inside NestedCheckbox, as the component is fully controlled
  • [EXTRA] Clicking a non-leaf checkbox will toggle all of its children on or off, depending its the current checked status

Demo

{
  "foo": true,
  "bar": false,
  "group1": {
    "level2": true,
    "level2option2": false,
    "nested": {
      "option": true
    }
  }
}

Source

The problem I have with this problem is that the interface doesn’t allow for recursiveness with reference to a top-level root.

With a top-level onChange, we could always mutate the initial value object directly and call onChange(value) to re-render the fully-controlled NestedCheckbox.

However, this means not being able to call <NestedCheckbox value={childValue} onChange={onChange} /> because that would replace the top-level value.

So instead, I created a refresh wrapper function that triggers the parent NestedCheckbox’s onChange, eventually reaching the parent:

const refresh = () => onChange(structuredClone(value))
  1. This is passed to child <NestedCheckbox value={childValue} onChange={refresh} />, since I don’t care about the child value updates and only want to refresh.
  2. I’m using structuredClone, which is a performant alternative to JSON.parse(JSON.stringify(value)).
import type { JSX } from 'preact'
import { useState } from 'react'

type CheckboxOption = {
  [key: string]: boolean | CheckboxOption
}

const isCheckboxOptionChecked = (value: boolean | CheckboxOption): boolean => {
  if (typeof value === 'boolean') {
    return value
  }

  return Object.keys(value).every((key: string) =>
    isCheckboxOptionChecked(value[key] as CheckboxOption),
  )
}

const toggleCheckboxOption = (
  parent: CheckboxOption,
  key: string,
  checked: boolean,
): CheckboxOption => {
  const currentValue = parent[key]

  if (typeof currentValue === 'boolean') {
    parent[key] = checked
  } else {
    Object.keys(currentValue).forEach((key) => {
      toggleCheckboxOption(currentValue, key, checked)
    })
  }

  return parent
}

const NestedCheckbox = ({
  value,
  onChange,
}: {
  value: CheckboxOption
  onChange: (value: CheckboxOption) => void
}) => {
  const refresh = () => onChange(structuredClone(value))

  return (
    <ol className="list-none">
      {Object.entries(value).map(([key, val]) => (
        <li key={key}>
          <label>
            <input
              type="checkbox"
              name={key}
              checked={isCheckboxOptionChecked(val)}
              onChange={(event: JSX.TargetedEvent<HTMLInputElement>) => {
                toggleCheckboxOption(
                  value,
                  key,
                  (event.target as HTMLInputElement).checked,
                )

                refresh()
              }}
            />
            &nbsp;
            {key}
          </label>

          {typeof val === 'object' && (
            <NestedCheckbox value={val} onChange={refresh} />
          )}
        </li>
      ))}
    </ol>
  )
}

const App = () => {
  const [value, onChange] = useState<CheckboxOption>({
    foo: true,
    bar: false,
    group1: {
      level2: true,
      level2option2: false,
      nested: {
        option: true,
      },
    },
  })

  return (
    <>
      <NestedCheckbox value={value} onChange={onChange} />
      <pre>{JSON.stringify(value, null, 2)}</pre>
    </>
  )
}

export default App