skip to Main Content

I’m writing a simple "select all" checkbox that checks/unchecks all other checkboxes, using react native with typescript and FluentUI checkboxes.

Summarized issue: The state of the "select all" checkbox seems to not update according to the state of the other checkboxes. It seems to be true when it should be false, and false when it should be true. Visually it is correct (appears checked), but functionally it keeps the previous state (true or false).

More context / details:

I’m running into an issue where the state of the checkbox does not seem to update correctly. When I check the "select all" checkbox, it selects all other checkboxes as desired. But if I then uncheck some of the checkboxes and then re-check the "select all" checkbox, instead of re-checking every checkbox, it un-checks all of them. The reverse also happens – if I have the "select all" checkbox unchecked, then check ALL other checkboxes, the "select all" checkbox appears visually selected, but if I click on it, it requires two clicks in order to un-check everything. This tells me that the state is opposite of what it should be – when I check all other checkboxes, it should change the state of the "select all" checkbox to true, but it still registers as false, thus causing me to need to click on it twice. And if I check the "select all" checkbox, then uncheck some other checkboxes, it should change the state of the "select all" checkbox to false, but it remains as true, thus causing all checkboxes to be un-checked when I click the "select all" checkbox again.

I’ve followed every possible example of how to make this work and the logic (in my opinion) seems sound, but yet I have this bug.

Here is how I have everything set up; I have two components, one for CheckboxAll, and one for CheckboxCard (it’s a checkbox that appears inside a Card component). The CheckboxCard renders inside the Card component, and the Card and CheckboxAll components render inside a parent component called Workbooks.

Here is the CheckboxAll component:

export const CheckboxAll: React.FunctionComponent<ICheckboxAllProps> = ( props: ICheckboxAllProps, ) => {
  return (
    <Checkbox
      checked={props.isChecked}
      onChange={(_, checked) => props.onChange(checked)}
    />
  );
};

And here is the code inside the parent Workbooks component:

export const Workbooks: React.FunctionComponent< IWorkbookProps > = (props: IWorkbookProps) => {
  // Some other code
  const [workbookIds, setWorkbookIds] = React.useState<string[]>([]);

  // This function toggles the individual checkboxes
  const handleCheck = (workbookId: string) => {
    setWorkbookIds((prev) => {
      const newState = prev.includes(workbookId)
        ? prev.filter(id => id !== workbookId)
        : [...prev, workbookId];

      return newState;
    });
  }

  // This function toggles the 'select all' checkbox
  const handleCheckAll = () => {
    setWorkbookIds((_) => {
      const newState = workbookIds.length === props.workbooks.length
        ? []
        : props.workbooks.map(workbook => workbook.id);

      return newState;
    });
  }

  return (
    <View>
      <CheckboxAll
        isChecked={workbookIds.length === props.workbooks.length}
        onChange={handleCheckAll}
      />
    {props.workbooks.map((workbook, index) => (
      <Card
        isChecked={workbookIds.includes(workbook.id)}
        onChange={() => handleCheck(workbook.id)}
      />))}
    </View>
  );
};

All other checkboxes work exactly as expected, it’s only the "select all" checkbox which seems to have its state not updating properly in that specific scenario.

I’ve tried every example I found on Google and I have also tried asking AI. The AI tends to simply re-write my code to do exactly what it is already doing.

Is there a way to better update the state of checkboxes that I have not found?

Thank you very much for your help.

2

Answers


  1. Chosen as BEST ANSWER

    I finally figured out the issue with the select all checkbox: React was still updating the states asynchronously thus the handleCheckAll function was not using the most up-to-date state. Even though I was using the functional state of useState, I had to actually use the "prev" variable the same way I did in handleCheck. This was the fix:

    const handleCheckAll = () => {
        setWorkbookIds((prev) => {
          const allIds = props.workbooks.map((workbook) => workbook.id);
          const newState = prev.length === props.workbooks.length ? [] : allIds;
    
          return newState;
        });
      };
    

  2. I found it much more intuitive to just add an isChecked prop to workbookIds and to manipulate that value. I didnt know which Checkbox component you were using so I just used react-native-paper

    const MyCheckBox = ({ title, isChecked, onPress }) => {
      return (
        <View style={styles.row}>
          <Checkbox
            status={isChecked ? 'checked' : 'unchecked'}
            onPress={onPress}
          />
          <Text>{title}</Text>
        </View>
      );
    };
    

    And then the main component:

    const initialWorkbookId = Array(5)
      .fill(null)
      .map((_, i) => ({
        title: `Item ${i + 1}`,
        id: i,
        isChecked: false,
      }));
    
    const getAllIsChecked = list=>list.every((item) => item.isChecked)
    
    export default function App() {
      const [workbookIds, setWorkbookIds] = useState(initialWorkbookId);
      const toggleIsChecked = useCallback((index) => {
        setWorkbookIds((prev) => {
          const newData = [...prev];
          newData[index].isChecked = !newData[index].isChecked;
          return newData;
        });
      }, []);
      const toggleAll = useCallback(() => {
        setWorkbookIds((prev) => {
          const allIsChecked = getAllIsChecked(prev)
          return prev.map((item) => {
            item.isChecked = !allIsChecked;
            return item;
          });
        });
      }, []);
      return (
        <SafeAreaView style={styles.container}>
          <MyCheckBox
            title="Check All"
            onPress={toggleAll}
            isChecked={getAllIsChecked(workbookIds)}
          />
          {workbookIds.map((item, i) => (
            <MyCheckBox
              title={item.title}
              onPress={() => toggleIsChecked(i)}
              isChecked={item.isChecked}
            />
          ))}
        </SafeAreaView>
      );
    }
    

    demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search