React Enjoyer
React Enjoyer

Reputation: 1402

How to 'Indeterminate' checkboxs?

Hello I want to know how to apply "Indeterminate" to my code Checkbox example:

enter image description here

I know the documentation is right there but I wasn't able to make it work this was my attempt and I would say I was close but clearly missing something very important

enter image description here

The idea is that when I click the "parent" one all of them get clicked, all of them changed to checked and all of them send the data to a variable and when I unclick they should remove it, and when I remove one in particular it should only remove that one in particular (that one is already working but idk if I have to add something else for the parent)

Individually they do what I want but I wanted to add the parent/main one so I can just check all of them:

enter image description here

This is my code:

[Edited code based on the help I have received so far]

//Functions

//set all students
const [allStudentsID, setAllStudentsID] = useState([]);
const setAllStudents = () => {
  setAllStudentsID(Array.isArray(estudiantes) ? allStudentsID.length && estudiantes.length === allStudentsID.length ? [] : estudiantes.map(x=> x.id):[]);
  console.log(allStudentsID)
}

The console.log looks like this:

enter image description here

//set individual one by one (works)
const [studentID, setStudentID] = useState([])
const setStudent = (estudiante) => {
  if(!studentID.find(id => id === estudiante)){
    setStudentID([ ... studentID, estudiante]); 
 }  
  else {
   setStudentID(studentID.filter(studentId => studentId !== estudiante)) 
   }  

   console.log(studentID)
}
Mapping/Render:

//Titles (not sure if I'm setting up correctly the checkbox)
                <thead>

                    <tr className="Lista">
                        <Checkbox 
                        color = "primary" 
                        id = "checkBox" 
                        onChange = {() => setAllStudents()}
                        checked={allStudentsID.length === estudiantes.length}
                        indeterminate={allStudentsID.length > 0 && allStudentsID.length < estudiantes.length}
                        />

                        <th>Nombre</th>
                        <th>Colegio</th>
                        <th>Grado</th>
                        <th>Accion</th>
                    </tr>
                </thead>

//Table (this works)
{estudiantes.map((estudiantes, index) => (
                <tr key={estudiantes.id || index}>
                <td>
                <Checkbox
                checked={!!studentID.find( id => id === estudiantes.uid)}
                color = "primary"
                id = "checkBox"
                onChange = {() => setStudent(estudiantes.uid, index)}
                inputProps={{ 'aria-label': 'controlled' }}
                />
                </td>

... some code that will finish the table

Upvotes: 3

Views: 8840

Answers (4)

Ben
Ben

Reputation: 5182

way less react-ish, a bit hacky, but short.

you could just assign it as a string (or not, because indeterminate="false".. is indeterminate !)

It becomes a standard 'visible' attribute on the input :

function SomeComponent({ indeterminate }) {
  const props = indeterminate ? { indeterminate: 'true or whatever string you want' } : {}
  return <input type="checkbox" {...props} />;
}

it then also becomes possible to be styled in plain css

input[type='checkbox']:indeterminate { ... }

Upvotes: 0

DoRyu Dracken
DoRyu Dracken

Reputation: 144

I made the code from scratch:

Comments in the code.

CodeSandbox

import { Fragment, useState } from "react";
import Box from "@mui/material/Box";
import Checkbox from "@mui/material/Checkbox";
import { Card } from "@mui/material";

const estudiantes = [
  { uid: 1, label: "Student 1" },
  { uid: 2, label: "Student 2" },
  { uid: 3, label: "Student 3" }
];

const App = () => {
  const [checkedStudents, setCheckedStudents] = useState([]);

  const handleChange1 = (isChecked) => {
    if (isChecked)
      return setCheckedStudents(
        estudiantes.map((estudiante) => estudiante.uid)
      );
    else setCheckedStudents([]);
  };

  const handleChange2 = (isChecked, uid) => {
    const index = checkedStudents.indexOf(uid);

    // The checked value is altered before the state changes for some reason is not a trully controlled component
    // So the next conditions are INVERTED.

    if (isChecked) return setCheckedStudents((state) => [...state, uid]);

    if (!isChecked && index > -1)
      return setCheckedStudents((state) => {
        state.splice(index, 1);
        return JSON.parse(JSON.stringify(state)); // Here's the trick => React does not update the f* state array changes even with the spread operator, the reference is still the same.
      });
  };

  return (
    <Fragment>
      {/* Parent */}

      <Checkbox
        checked={checkedStudents.length === estudiantes.length}
        indeterminate={
          checkedStudents.length !== estudiantes.length &&
          checkedStudents.length > 0
        }
        onChange={(event) => handleChange1(event.target.checked)}
      />

      {/* Childrens */}
      <Box sx={{ display: "flex", flexDirection: "column", ml: 3 }}>
        {checkedStudents &&
          estudiantes.map((estudiante) => (
            <Checkbox
              key={estudiante.uid}
              checked={checkedStudents.includes(estudiante.uid)}
              onChange={(event) =>
                handleChange2(event.target.checked, estudiante.uid)
              }
              inputProps={{ "aria-label": "controlled" }}
            />
          ))}
      </Box>

      <h3>ID's: {JSON.stringify(checkedStudents)}</h3>
    </Fragment>
  );
};

export default App;

Upvotes: 2

React Enjoyer
React Enjoyer

Reputation: 1402

I have found a solution but had to change to regular checkbox which then leave this topic half resolved because so far we haven't find a solution to do it properly with Material UI checkbox anyways I'll leave it here this is for the regular input checkbox.

//Variable that will provide the data (mine updates from firebase so not gonna add all that code)
const [estudiantes, setEstudiantes] = useState([]);

//Variable that will hold all the data
const [studentsID, setStudentsID] = useState([]);

//Function
const handleChange = (e, data) => {
  const { name, checked } = e.target;
  if (checked) {
    // if cheked and selectall checkbox add all fileds to selectedList
    if (name === "allSelect") {
      setStudentsID(estudiantes);
    } else {
      // if cheked and specific checkbox add specific field to selectedList
      setStudentsID([...studentsID, data]);
    }
  } else {
    // if uncheked and selectall checkbox add remove all fileds from selectedList
    if (name === "allSelect") {
      setStudentsID([]);
    } else {
      // if uncheked and specific checkbox remove specific field from selectedList
      let tempuser = studentsID.filter((item) => item.id !== data.id);
      setStudentsID(tempuser);
    }
  }
  console.log(studentsID)
};

//Main select checkbox
<input
   type="checkbox"
   className="form-check-input"
   name="allSelect"
   checked={studentsID?.length === estudiantes?.length}
   onChange={(e) => handleChange(e, estudiantes)}
/>

//Checkboxs inside the .map

<tbody>
{estudiantes.map((estudiantes, index) => (
   <tr key={estudiantes.id || index}>
   <td>
      <input
      type="checkbox"
      className="form-check-input"
      name={estudiantes.uid}
      checked={studentsID.some((item) => item?.uid === estudiantes.uid)}
      onChange={(e) => handleChange(e, estudiantes)}
      />
   </td>

//Some nonrelevant code for this question ...

};

Disclaimer this answer is not mine I found it from a tutorial: All Select Checkbox in React JS and an example: Example I just adapted it to my needs.

If anyone finds proper solution with Material UI checkbox let me know.

Upvotes: 0

Junaid Faryad
Junaid Faryad

Reputation: 1707

As per your code and question, when you're using estudiantes.uid to handle the checkboxes state, it works, but in setAllStudents function, you're usingx.label, which doesn't exist in the array, that's why your console log shows the undefined. Maybe try updating the function to return the id from the function.

Initialize the state as an emtpy array

const [allStudentsID, setAllStudentsID] = useState([]);

Than update the setAllStudents to check both conditions, if all the checkboxes are already selected, than reset the state array to empty array, otherwise select all.

const setAllStudents = () => {
  setAllStudentsID(Array.isArray(estudiantes) ? allStudentsID.length && estudiantes.length === allStudentsID.length ? [] : estudiantes.map(x=> x.id):[]);
  console.log(allStudentsID)
}

Please also provide the checked and indeterminate props to the checkbox in the header.

<Checkbox 
  color = "primary" 
  id = "checkBox" 
  onChange = {() => setAllStudents()}
  checked={allStudentsID.length === estudiantes.length} // if both are equal, that means all were already selected
  indeterminate={allStudentsID.length > 0 && allStudentsID.length < estudiantes.length}
/>

Upvotes: 3

Related Questions