0

I've found a solution for programmatically resetting the options on my list of checkboxes, however, for some reason, the graphical element itself does not change to match the state. I assumed it would do it automatically on state change, however, it appears there has to be a way that i'm supposed to be doing it sepearately, in addition to what I'm currently doing.

First, I initialize my checkbox options:

const [categoryFilterOptions, setCategoryFilterOptions] = useState([{label: 'Category1', checked: true}, {label: 'Category2', checked: true}, {label: 'Category3', checked: true}, {label: 'Category4', checked: true}])

This is passed into my checklist component on render and processed like this:

 {checks.map((checkItem, i) => (
                 <Form.Check
                    key={i}
                    id={i}
                    type='checkbox'
                    label={checkItem.label}
                    defaultChecked={checkItem.checked}
                    onClick={() => {setCheck(i)}}
                ></Form.Check>
            )) }

Then to reset them, I have a button:

<Button style={{width: '100%'}} onClick={() => {resetAllChecks()}}>Reset Filter</Button>

Which calls the method resetAllChecks() which just sets my check options back to their initial state:

const resetAllChecks = () => {
    setFamilyFilterOptions([{label: 'String', checked: true}, {label: 'Percussion', checked: true}, {label: 'Brass', checked: true}, {label: 'Wind', checked: true}, {label: 'Electronic', checked: true}, {label: 'Keyboard', checked: true}])
    setCategoryFilterOptions([{label: 'Contemporary', checked: true}, {label: 'Orchestral', checked: true}, {label: 'Traditional', checked: true}, {label: 'Vocal', checked: true}])
}

I'm really not sure why it refuses to reset the graphical element of the checkboxes, because the list get's reset perfectly fine, yet the checkboxes don't.

Edit: Here is some of the surrounding code, which may be better for context?

Programmatically, it works perfect on the data side, it's just the graphics not updating, so i'm not sure if this will be a ton of help, but it's worth a shot!

const DropdownChecklist = ({disabled, checkOptions, returnChecksState}) => {
    const [checks, setChecks] = useState([...checkOptions]); 

    function setCheck(id){
        var newArray = [...checks]
        var newElement = checks[id];
        newElement.checked = (!newElement.checked)
        newArray[id] = newElement;
        setChecks(newArray);
        returnChecksState(checks);
    }

    console.log(checks)
    
    return (
        <Form className={styles.checkMenu}>
            <fieldset disabled={disabled}>
            {checks.map((checkItem, i) => (
                <Form.Check
                    key={i}
                    id={i}
                    type='checkbox'
                    label={checkItem.label}
                    checked={checkItem.checked}
                    onChange={() => {setCheck(i)}}
                ></Form.Check>
            )) }
            </fieldset>
        </Form>
    )
}

export default DropdownChecklist

Edit 2: Even more context

<Col>
     <h5 style={{textAlign: 'center', color: 'white'}}>Category Filtering: </h5>
     <DropdownChecklist disabled={false} checkOptions={categoryFilterOptions} returnChecksState={setCategoryFilterOptions}></DropdownChecklist>
 </Col>

Edit 3: I tried using the solution offered Here, and it's still not working, however, i have now realized that somehow, my component logic which is resetting my checks is somehow directly touching my DEFAULT_CHECKS values which makes no sense to me, especially since it is a const value, that shouldn't even be able to change

Robby Hoover
  • 71
  • 1
  • 11
  • 3
    Can you check by replacing defaultChecked with checked? – Shipra Sarkar Feb 16 '22 at 05:22
  • @ShipraSarkar Unfortunately, that didn't change anything, except throw some error in the console about providing a checked prop without an onchange handler – Robby Hoover Feb 16 '22 at 05:55
  • 1
    @RobbyHoover You probably could define onChange for each of the checkboxes, then. [Controlled components](https://reactjs.org/docs/forms.html#controlled-components) are usually preferred except in special cases. They give you more power over programmatically tracking/setting input values. – Serlite Feb 16 '22 at 06:32
  • @Serlite i tried replacing onClick with onChange and it still yields the same issue as before. How would i go about implementing controlled components into this? – Robby Hoover Feb 16 '22 at 07:06
  • 1
    @RobbyHoover Sure! I've posted an answer with some more details - let me know if anything is amiss. I'm hoping that a Form.Check exposes the same props that a standard Input would. – Serlite Feb 16 '22 at 16:52

3 Answers3

2

The defaultChecked prop is only meant to be used with uncontrolled components, which under most circumstances you will not need. The behaviour you're seeing is expected:

Changing the value of defaultValue attribute after a component has mounted will not cause any update of the value in the DOM.

You should generally prefer controlled components when you want to programmatically manipulate your input values. The idea is that the input should always get its value from the state in React, and interaction with the input should update the state accordingly.

For a checkbox, that means setting the checked and onChange props, so it could look something like:

{checks.map((checkItem, i) => (
    <Form.Check
        key={i}
        id={i}
        type='checkbox'
        label={checkItem.label}
        checked={checkItem.checked}
        onChange={() => {setCheck(i)}}
    ></Form.Check>
)) }

Now when you call resetAllChecks(), the checkboxes should re-render with the latest values in the state.

Serlite
  • 12,130
  • 5
  • 38
  • 49
  • So, the code you posted is basically verbatim what i changed my code to when you told me about onChange and all that, and with that code it still does not work as intended :/ The idea of controlled components makes a lot of sense though! I think something else might be amiss somwhere, i just have no idea what it could be. Maybe some disconnect between check and its graphical representation? – Robby Hoover Feb 16 '22 at 17:36
  • i do get a new error now however, it says 'Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode' – Robby Hoover Feb 16 '22 at 17:41
  • @RobbyHoover That's quite puzzling - from what I can read, these properties are the way you turn Form.Check into a controlled component. Can you add some logging perhaps into your mapping/updating logic, to see if any values are incorrect at time of render, and to ensure a re-render is actually being triggered? Regarding the error, some features of react-bootstrap may be triggering it: https://stackoverflow.com/a/64325602/1387174 – Serlite Feb 16 '22 at 17:53
  • yeah man, it's got me super confused. I logged the checkboxes every time they are updated, and on the data side it works perfectly. I'm gonna post some more of the code surrounding it and maybe i messed something up there? Do you think this could be some issue with React-Bootstrap? – Robby Hoover Feb 16 '22 at 23:04
  • @RobbyHoover Hmmm...something seems a bit weird to me - I imagine `DropdownChecklist` is meant to be a function component. But generally I'd only expect function components to accept a single parameter, `props`, rather than a custom number of them. Anything you need to pass in would be accessed via that parameter, like `props.disabled` or `props.checkOptions`. Perhaps this is why React is behaving so strangely here? – Serlite Feb 17 '22 at 00:02
  • I'm not sure. I use this style of component all throughout my project, but if you can point me in the direction on how to rewrite it in a different way, i will be glad to try it! I also added a bit more context that could maybe help? – Robby Hoover Feb 17 '22 at 00:34
  • @RobbyHoover My mistake - I didn't look closely enough, you are passing in a single object (treated as the props). Everything looks...fine, which is weird. What happens if you swap out `Form.Check` for just a plain `input type='checkbox'`? I wonder if the component doesn't expose the checked/onChange properties correctly... – Serlite Feb 17 '22 at 01:11
  • @RobbyHoover Something else to consider is using a more globally-unique string (than just the index) for the id attribute - if they're possible being duplicated elsewhere, that could also cause some weirdness. – Serlite Feb 17 '22 at 01:14
  • replacing it with the vanilla checkbox yields the same result. Also i will keep that in mind, i will try and figure out something to implement in place of id! Thank you for all your time spent helping me. If you think of any other things that could be causing it, please lemme know! – Robby Hoover Feb 17 '22 at 01:35
1

You should not try to alter the Parent state along with the child's state at the same time. let the parent handle the changes and pass them to the new props.

import React, { useState } from 'react';
import { Col, Form, Button } from 'react-bootstrap';

export default function App() {
  const [categoryFilterOptions, setCategoryFilterOptions] = useState([
    { label: 'Category1', checked: true },
    { label: 'Category2', checked: true },
    { label: 'Category3', checked: true },
    { label: 'Category4', checked: true },
  ]);

  const resetAllChecks = () => {
    setCategoryFilterOptions(
      categoryFilterOptions.map((option) => ({
        label: option.label,
        checked: true,
      }))
    );
  };

  return (
    <Col>
      <h5 style={{ textAlign: 'center', color: 'black' }}>
        Category Filtering:
      </h5>
      <DropdownChecklist
        disabled={false}
        checkOptions={categoryFilterOptions}
        returnChecksState={setCategoryFilterOptions}
      ></DropdownChecklist>
      <Button
        style={{ width: '100%' }}
        onClick={() => {
          resetAllChecks();
        }}
      >
        Reset Filter
      </Button>
    </Col>
  );
}

const DropdownChecklist = ({ disabled, checkOptions, returnChecksState }) => {
  function setCheck(id) {
    var newArray = [...checkOptions];
    var newElement = checkOptions[id];
    newElement.checked = !newElement.checked;
    newArray[id] = newElement;
    returnChecksState(newArray);
  }

  return (
    <Form>
      <fieldset disabled={disabled}>
        {checkOptions.map((checkItem, i) => (
          <Form.Check
            key={checkItem.label}
            id={i}
            type="checkbox"
            label={checkItem.label}
            checked={checkItem.checked}
            onChange={() => {
              setCheck(i);
            }}
          ></Form.Check>
        ))}
      </fieldset>
    </Form>
  );
};

You can see a working example here

Ashu Sahu
  • 417
  • 6
  • 7
  • This has done it! Thank you my friend! This has been bothering me for months now! I will study the changes you've made so i don't repeat this mistake in the future! – Robby Hoover Mar 03 '22 at 07:01
0

Don't use uncontrolled components sir bad practice.

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 01 '22 at 09:20