Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some way to specify indeterminate checkboxes #1798

Closed
sophiebits opened this issue Jul 8, 2014 · 30 comments
Closed

Add some way to specify indeterminate checkboxes #1798

sophiebits opened this issue Jul 8, 2014 · 30 comments

Comments

@sophiebits
Copy link
Collaborator

There should be a way do do <input type="checkbox" indeterminate={true} /> or similar – right now the attribute is ignored. Need to figure out how this interacts with checked though.

@zpao
Copy link
Member

zpao commented Jul 9, 2014

https://mdn.mozillademos.org/en-US/docs/Web/CSS/:indeterminate$samples/Example?revision=601267 is fun to play with.

Not impossible to support, but that's annoying.

@sophiebits
Copy link
Collaborator Author

Sorry, what's annoying?

@zpao
Copy link
Member

zpao commented Jul 9, 2014

The correct combination of indeterminate and checked. (eg indeterminate=true and checked=true is impossible, indeterminate=true and checked=false is ok edit, even when you explicitly say checked=false)

@dmitry
Copy link

dmitry commented Dec 22, 2014

I have the same issue. Is there are any ability to set indeterminate property for the checkbox within React?

Found the solution:

  componentDidMount: ->
    $('input', this.getDOMNode()).prop({
      indeterminate: true,
      checked: false
    })

or just a react version:

  componentDidMount: ->
    checkbox = this.refs.checkbox.getDOMNode()
    checkbox.indeterminate = true
    checkbox.checked = false

But what are the drawbacks, except the jquery/zepto are used in this example?

Isn't in that case an element will be always be rerendered?

@zpao
Copy link
Member

zpao commented Dec 22, 2014

The drawback is that your are mutating the DOM manually, which kinda sucks. You also want to make sure this happens in the right place for each subsequent render.

@dmitry
Copy link

dmitry commented Dec 23, 2014

You also want to make sure this happens in the right place for each subsequent render.

How to make sure?
And as I understand componentDidMount happens after virtual DOM is placed in a real DOM. Between rendering to the real DOM and changing the input it may take some time. In some situations browser reflow can be done few times?

@zpao
Copy link
Member

zpao commented Dec 23, 2014

Well, on a re-render componentDidMount will not get called so you also need to do it in componentDidUpdate to make sure it happens there if needed.

@azich
Copy link
Contributor

azich commented Jul 22, 2015

Pulling in my research from #2973 (and reviving this thread):

Chrome, Firefox, Safari, IE and other modern browsers support the notion of a checkbox in an indeterminate state which can be set or cleared using the indeterminate property of a checkbox input node. (It cannot be set using attributes.)

This state is most commonly used at the top of a list or tree of checkboxes to indicate that some but not all of the checkboxes underneath are checked.

This behavior seems reasonably well-defined in modern Chrome, Firefox, Safari, and IE. If the checkbox's indeterminate property is set, the checkbox renders its indeterminate state regardless of what its checked state is (or if its checked state is programmatically toggled) and clicking on a checkbox clears its indeterminate state.

Unfortunately there is a little bit of inconsistency. In Chrome, Firefox, and Safari clicking an indeterminate checkbox clears the indeterminate state and also toggles the checked state (triggering both a change and click event) while in IE it just clears the indeterminate state and leaves the checked state alone (triggering only a click event).

  • Should React support an indeterminate prop for checkbox inputs?
  • Should it be supported for uncontrolled checkboxes (via an defaultIndeterminate or similar prop)?
  • Should clicking an indeterminate checkbox follow the browser's checked-toggling behavior?

[Claims above come from playing with this jsFiddle in Chrome 40, Firefox 35, Safari 7, and IE 11]

@sophiebits
Copy link
Collaborator Author

Maybe we should do checked equal to true, false, or 'indeterminate'? :\ I am clearly an API design genius.

@azich
Copy link
Contributor

azich commented Aug 12, 2015

If you have one checkbox that's checked and one checkbox that's not checked, put both of them in an indeterminate state then click both of them, the two checkboxes will have opposite checked states in all of the browsers listed above. This means there are still two distinct bits of information, at least in the browser's implementation.

@sophiebits
Copy link
Collaborator Author

Ooh, good find.

@jlas
Copy link

jlas commented Nov 3, 2015

checked should be either true/false

indeterminate should be either true/false

if indeterminate === true and checked state is updated, indeterminate becomes false

http://www.w3.org/TR/2014/WD-html51-20140617/forms.html#checkbox-state-(type=checkbox)

@jquense
Copy link
Contributor

jquense commented Dec 14, 2015

This means there are still two distinct bits of information, at least in the browser's implementation.

That I think is critical to maintain in React's support, a checkbox can be indeterminate: checked, or indeterminate: unchecked.

The frustrating problem though is that the checked change toggles resets indeterminate as @jlas notes. perhaps some variation of the controlled/uncontrolled pattern would work?

<input type='checkbox' 
  checked={checked} 
  defaultIndeterminate={!checked} 
/>

<input type='checkbox' 
  checked={this.state.checked} 
  indeterminate={this.state.indeterminate} 
  onChange={({ target: {checked} }) => 
    this.setState({ checked, indeterminate: false }) }
/>

The downside to something like that is that it adds a bunch more logic to the DOMInput wrappers, which I know was something folks were trying to make lighter weight

@quantizor
Copy link
Contributor

Can I just point out that there is no indeterminate HTML attribute? Developers have always had to set the status via JS. It seems against React's nature to patch something that doesn't actually exist in declarative DOM land?

@syranide
Copy link
Contributor

It seems against React's nature to patch something that doesn't actually exist in declarative DOM land?

@yaycmyk Correct. IMHO it's arguable, but server-rendering in a way necessitates it and seems like a hard problem to otherwise decide where to draw the line. It's really weird that there isn't an attribute for it. Interesting.

@jquense
Copy link
Contributor

jquense commented Dec 14, 2015

React adds declarative layers over a lot of imperative DOM API's, which is to say just b/c there is an HTML attribute doesn't actually mean its a declarative API, that and the prop's are sugar over the js API's anyway.

Agree that its a bit weird to add in an "attribute", but that's the main React API surface for interacting with DOM objects, so if possible i'd be nice to have. It does fall into the "a bit outside the norm" for react but not very far I think. CC also the discussions about adding a declarative focus() api (that's not autoFocus).

@sophiebits
Copy link
Collaborator Author

Our form components go a bit beyond what normal HTML attributes give you.

@syranide
Copy link
Contributor

@spicyj Huh? Yes they add additional run-time functionality (i.e. controlled), but the initial state of them all is entirely captured by attributes right? Or am I missing something.

@sophiebits
Copy link
Collaborator Author

Yeah, that's true.

@cema-sp
Copy link

cema-sp commented Jun 24, 2016

It is a definitely a good idea to add indeterminate property. Why hasn't it been solved?
I currently use:

if (shoudNotBeChecked) {
  this.refs.checkbox.indeterminate = false;
  this.refs.checkbox.checked = false;
} else if (shoudBeChecked) {
  this.refs.checkbox.indeterminate = false;
  this.refs.checkbox.checked = true;
} else {
  this.refs.checkbox.indeterminate = true;
}

...

render() {
  return (
    <input
      type="checkbox"
      name="Check"
      ref="checkbox"
      onChange={this.props.onCheck}
    />,
  );
}

@syranide
Copy link
Contributor

@cema-sp My recommendation in the mean time is to just create a CheckboxInput component that does that internally the way you want it. If React ends up implementing this then the implementation will end up being identical anyway, but part of the native input wrapper.

@kolodny
Copy link
Contributor

kolodny commented Oct 28, 2016

FWIW, I discovered a clean way to do this as follows:

<input type="checkbox" ref={elem => elem && (elem.indeterminate = isIndeterminate)} />

http://codepen.io/anon/pen/LRoLXZ?editors=0010

@quantizor
Copy link
Contributor

@kolodny I wouldn't really call that clean. Making a HOC would be a better approach. You're hijacking refs to make a side effect.

@mnpenner
Copy link
Contributor

I'm trying to create a HOC but it doesn't seem to work unless I add a 0-ms timeout. Is this expected?

import React, {PropTypes} from 'react';

export default class Checkbox extends React.Component {
    static propTypes = {
        indeterminate: PropTypes.bool,
        checked: PropTypes.bool,
    };
    
    componentDidMount() {
        this.el.indeterminate = this.props.indeterminate;
        this.el.checked = this.props.checked; // fix for IE8
    }

    componentDidUpdate(prevProps, prevState) {
        if(prevProps.indeterminate !== this.props.indeterminate) {
            setTimeout(() => {
                this.el.indeterminate = this.props.indeterminate;
            }, 0);
        }
    }

    render() {
        const {indeterminate, ...attrs} = this.props;
        return <input ref={el => {this.el = el;}} type="checkbox" {...attrs}/>;
    }
}

@sassanh
Copy link
Contributor

sassanh commented Mar 1, 2017

It'd be great if we were able to set indeterminate as react prop.

@gaearon
Copy link
Collaborator

gaearon commented Oct 1, 2017

It seems like we’re not doing it because it’s impossible to support with server rendering, and we’re currently only supporting a subset of attributes/properties that are compatible both with client and server rendering.

If you need it, it is trivial to implement with your own component:

https://codepen.io/gaearon/pen/aLyEmr?editors=0010

class Checkbox extends React.Component {
  componentDidMount() {
    this.el.indeterminate = this.props.indeterminate;
  }

  componentDidUpdate(prevProps) {
    if (prevProps.indeterminate !== this.props.indeterminate) {
      this.el.indeterminate = this.props.indeterminate;
    }
  }

  render() {
    return (
      <input {...this.props} type="checkbox" ref={el => this.el = el} />
    );
  }
}

Checkbox.defaultProps = {
  indeterminate: false,
};

// Usage
ReactDOM.render(
  <Checkbox indeterminate={true} />,
  document.getElementById('root')
);

There might be a few more gotchas (see the thread above) but I hope this shows that it’s easy to achieve with React even without React directly supporting it.

@willem-aart
Copy link

willem-aart commented Aug 29, 2018

Is this a fine approach, compared the solution @gaearon suggested?

export default function Checkbox(props) {
  const setCheckboxRef = checkbox => {
    if (checkbox) {
      checkbox.indeterminate = props.isIndeterminate;
    }
  };

  return (
    <input
      type="checkbox"
      ref={setCheckboxRef}
    />
  );
}

Checkbox.propTypes = {
  isIndeterminate: PropTypes.bool,
};

Checkbox.defaultProps = {
  isIndeterminate: false
};

@gaearon
Copy link
Collaborator

gaearon commented Aug 29, 2018

Yes, that should be fine too.

@artalar
Copy link

artalar commented Apr 3, 2023

@gaearon is this possible with RSC?

@sophiebits
Copy link
Collaborator Author

Since there is no indeterminate attribute in HTML it’s not possible to make an indeterminate checkbox that appears correctly before any JS is run. However with RSC you can still make a client component that sets the indeterminate property upon mount and render it from a server component.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.