0

I am trying to implement a triple-state checkbox to react. The repeated clicking on the checkbox would cycle through blank->checked->crossed->blank->... like this. With all look around and searching for answers, I came along a reference: Indeterminate checkbox in React JSX.

I tried something like this:

export default function App() {
  const [state, setState] = React.useState({
    data: [
      {
        id: "1",
        one: 0
      },
      {
        id: "2",
        one: 0
      }
    ]
  });
  const indetSetter = React.useCallback(
    (el, id) => {

      if (el) {
        if (el.id === id && state.data.map((datum) => datum.one === 2)) {
          el.indeterminate = true;
        }
      }
    },
    [state]
  );

  const advance = (id, e) => {
    setState((state) => {
      
      state.data.map((datum) =>
        datum.id === id ? { ...datum, one: (datum.one + 1) % 3 } : { ...datum }
      );
    });

  return (
    <>
      {state.data.map((item) => {
        // console.log("show", item);
        return (
          <input
            key={item.id}
            id={item.id}
            type="checkbox"
            checked={item.one === 1}
            ref={(el) => indetSetter(el, item.id)}
            onChange={(e) => advance(item.id, e)}
          />
        );
      })}
    </>
  );
}

But eventually it throws me an error: can't access property "indeterminate", el is null or can't access property "data", state is undefined.

Working snippet: https://codesandbox.io/s/relaxed-hill-qqb4y

Any help to resolve the same is highly appretiated.

Thanks in advance :)

program_bumble_bee
  • 439
  • 2
  • 25
  • 57
  • try using `useLayoutEffect` so the code is executed after dom is available – diedu Oct 10 '20 at 03:04
  • @diedu I am not sure abot the code that I am trying to execute. Using `useLayoutEffect` will compliment the logic I have written? If you can help with that – program_bumble_bee Oct 10 '20 at 12:32
  • sorry, my comment is quite wrong, you aren't using any `useEffect` hook. Looking at it in more detail, I think you should create a separate component to encapsulate the logic of the indeterminate checkbox. BTW the codesandbox is very different to the code in the question – diedu Oct 11 '20 at 04:13

1 Answers1

2

As I commented, you need to encapsulate the indeterminate logic in a separate component to be able to update the dom when value changes via useEffect as explained here

This is the component for a single indeterminate checkbox

IndeterminateInput.js

import React, { useCallback, useEffect, useRef } from 'react';

export default function IndeterminateInput({ id, name, value, onChange }) {
  const el = useRef(null);

  const onChangeHandler = useCallback(
    event => {
      onChange({ id, name, value: (value + 1) % 3 });
    },
    [id, name, value, onChange],
  );

  useEffect(() => {
    el.current.checked = value === 1;
    el.current.indeterminate = value === 2;
  }, [value]);

  return (
    <input type="checkbox" name={name} ref={el} onChange={onChangeHandler} />
  );
}

And then you render one IndeterminateInput for each item in your main component passing the necessary props

export default function App() {
  const [state, setState] = ...

  const onChange = useCallback((item) => {
    const { id, value } = item;
    setState((prevState) => {
      return {
        data: prevState.data.map((datum) => {
          return id === datum.id
            ? {
                ...datum,
                one: value
              }
            : datum;
        })
      };
    });
  }, []);

  return (
    <>
      {state.data.map((item, i) => {
        return (
          <IndeterminateInput
            key={item.id}
            id={item.id}
            name={item.name}
            value={item.one}
            onChange={onChange}
          />
        );
      })}
    </>
  );
}

you can see it working in here: https://codesandbox.io/s/stoic-euler-e97so?file=/src/App.js

diedu
  • 19,277
  • 4
  • 32
  • 49
  • Thanks for this. Just one thing I need to ask. If we have more than one element that needs to be referenced, we don't need to create dynamic refs() for multiple elements? The DOM will automatically detect each current element that needs to be referenced? – program_bumble_bee Oct 11 '20 at 09:30
  • 1
    @bubble-cord I could also have gone with something like this https://stackoverflow.com/questions/54633690/how-can-i-use-multiple-refs-for-an-array-of-elements-with-hooks but in this case it makes more sense to create a reusable component en let it manage the dom ref inside – diedu Oct 11 '20 at 22:13