25

How do I render an indeterminate checkbox via JSX?

Here's what I've tried:

function ICB({what}) {
  return <input type="checkbox"
                checked={what === "checked"} 
                indeterminate={what === "indeterminate"} />;
}

However, indeterminate is not an attribute on the HTMLElement, but a property. How do I set properties from React / JSX?


Solution:

As most of the answers below use findDOMNode or string refs, both of which are no longer considered good practice in React, I've written a more modern implementation:

function ICB() {
  const [state, setState] = React.useState(0);
  const indetSetter = React.useCallback(el => {
    if (el && state === 2) {
      el.indeterminate = true;
    }
  }, [state]);
  const advance = () => setState(prev => (prev + 1) % 3);
  
  return <input type="checkbox"
                checked={state === 1}
                ref={indetSetter}
                onClick={advance} />;
}
                
ReactDOM.render(<ICB />, document.getElementById("out"));
<div id="out"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
P Varga
  • 19,174
  • 12
  • 70
  • 108

8 Answers8

22

You can also use the ref function directly:

ReactDOM.render(
  <label>
    <input
      type="checkbox"
      ref={input => {
        if (input) {
          input.indeterminate = true;
        }
      }}
    />
    {' '}
    Un test
  </label>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Kévin Berthommier
  • 1,402
  • 15
  • 15
  • 1
    Who downvoted this answer?! This is by far the easiest and most straight forward solution. – vog Oct 29 '19 at 23:06
  • Is it possible to reference itself if passed an `indeterminate` prop in the JSX? It doesn't seem to work when I try. Something like... `if (input && this.input.props.indeterminate)` – wongz Feb 01 '23 at 19:59
13

I would probably create a composite component that encapsulates the necessary hooks to set or unset the checkbox's indeterminate property. It looks like you're using ES2015 syntax, so I'll use some of those features here.

class IndeterminateCheckbox extends React.Component {
  componentDidMount() {
    if (this.props.indeterminate === true) {
      this._setIndeterminate(true);
    }
  }

  componentDidUpdate(previousProps) {
    if (previousProps.indeterminate !== this.props.indeterminate) {
      this._setIndeterminate(this.props.indeterminate);
    }
  }

  _setIndeterminate(indeterminate) {
    const node = React.findDOMNode(this);
    node.indeterminate = indeterminate;
  }

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

// elsewhere

render() {
  return <IndeterminateCheckbox
           checked={this.props.state === "checked"} 
           indeterminate={this.props.state === "indeterminate"} />
}

Working example: https://jsbin.com/hudemu/edit?js,output

Michelle Tilley
  • 157,729
  • 40
  • 374
  • 311
10

You can use the componentDidMount step (which is invoked after the initial rendering) to set that property:

componentDidMount() {
    React.findDOMNode(this).indeterminate = this.props.state === "indeterminate";
}

If you want that property to be updated with subsequent renders, do the same thing in componentDidUpdate also.

manji
  • 47,442
  • 5
  • 96
  • 103
  • That would not react to new props, I guess I could use `componentDidUpdate`. But that's not run on the initial render... Is there an elegant way? – P Varga Aug 21 '15 at 11:49
  • 1
    `componentDidUpdate` is not called on first render, `componentDidMount` is not called on subsequent renders. You have to use both. – manji Aug 21 '15 at 11:53
  • It looks like React allows custom `data-` attributes, but nothing else: https://facebook.github.io/react/docs/jsx-gotchas.html#custom-html-attributes Here's the GitHub issue where they're tracking general custom attributes support: https://github.com/facebook/react/issues/140 For now, `findDOMNode` seems to be the best workaround. – Kyle Aug 21 '15 at 11:53
  • not very pretty, but you can always have two inputs, one with and one without, where you return the first if indeterminate state is true, and the other if its false.. – thsorens Aug 21 '15 at 11:56
  • 1
    This is how React works when you need to operate on dom nodes. – manji Aug 21 '15 at 12:03
  • @Kyle The issue is that `indeterminate` is not even an attribute. It's a property. – P Varga Aug 21 '15 at 17:06
  • React.findDOMNode is [deprecated](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#two-packages-react-and-react-dom). Use [this.refs.domref](http://facebook.github.io/react/docs/more-about-refs.html) instead. – Roger Ramos Apr 22 '16 at 19:12
5

I'd suggest creating a simple component (code ported from coffeescript so mind you, might have some simple typos):

const React = require('react');

module.exports = class IndeterminateCheckbox extends React.Component {
    componentDidMount() {
        this.refs.box.indeterminate = this.props.indeterminate;
    }

    componentDidUpdate(prevProps, prevState) {
        if(prevProps.indeterminate !== this.props.indeterminate) {
            this.refs.box.indeterminate = this.props.indeterminate;
        }
    }

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

}

Now you have a simple component that behaves exactly like a checkbox, that supports the indeterminate prop. Note there's plenty of room for improvements here, namely setting propTypes and proper defaults for some props, and of course implementing componentShouldUpdate to only do something when needed.

kastermester
  • 3,058
  • 6
  • 28
  • 44
3

An alternative would be to use a ref attribute with a callback to set the property on the DOM node. For example:

render: function() {
  return (
    <input
        type="checkbox"
        ref={function(input) {
          if (input != null) {
            React.findDOMNode(input).indeterminate = this.props.indeterminate;
          }}
        {...this.props} />
  )
}
Paul Weaver
  • 284
  • 5
  • 9
  • Downvoting because with a `ref` you already have access to the DOM node. There is no need to do `React.findDOMNode(input)`. – Lucky Soni Sep 04 '20 at 07:50
1

Dont use React.findDOMNode(this). It is risky.

export class SelectAll extends Component {
constructor(props) {
    super(props);
    this.state = {
        checked: false
    };
    this.myRef = React.createRef();
    this.onChange = this.onChange.bind(this);
    this.update = this.update.bind(this);
}

onChange(e) {
    const checked = e.target.checked;
    this.setState({
        checked: checked
    });
    this.selectAllNode.indeterminate = false;
}

update(state: {
    checked: Boolean,
    indeterminate: Boolean
}) {
    this.setState({
        checked: state.checked
    });
    this.myRef.current.indeterminate = state.indeterminate;
}

render() {
    return ( <
        input type = "checkbox"
        name = "selectAll"
        checked = {
            this.state.checked
        }
        onChange = {
            this.onChange
        }
        ref = {
            this.myRef
        }
        />
    );
}
}
Vimal Sharma
  • 69
  • 1
  • 1
-1

React v15 implementation:

import React from 'react';

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

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

    render() {
        const {indeterminate, ...attrs} = this.props;
        return <input ref={el => {this.el = el}} type="checkbox" {...attrs}/>;
    }
}
mpen
  • 272,448
  • 266
  • 850
  • 1,236
-2

Taken from my tutorial which shows how this works with the recent React features. I hope this helps someone who stumbles upon this older question:

const App = () => {
  const [checked, setChecked] = React.useState(CHECKBOX_STATES.Empty);
 
  const handleChange = () => {
    let updatedChecked;
 
    if (checked === CHECKBOX_STATES.Checked) {
      updatedChecked = CHECKBOX_STATES.Empty;
    } else if (checked === CHECKBOX_STATES.Empty) {
      updatedChecked = CHECKBOX_STATES.Indeterminate;
    } else if (checked === CHECKBOX_STATES.Indeterminate) {
      updatedChecked = CHECKBOX_STATES.Checked;
    }
 
    setChecked(updatedChecked);
  };
 
  return (
    <div>
      <Checkbox
        label="Value"
        value={checked}
        onChange={handleChange}
      />
 
      <p>Is checked? {checked}</p>
    </div>
  );
};

const Checkbox = ({ label, value, onChange }) => {
  const checkboxRef = React.useRef();
 
  React.useEffect(() => {
    if (value === CHECKBOX_STATES.Checked) {
      checkboxRef.current.checked = true;
      checkboxRef.current.indeterminate = false;
    } else if (value === CHECKBOX_STATES.Empty) {
      checkboxRef.current.checked = false;
      checkboxRef.current.indeterminate = false;
    } else if (value === CHECKBOX_STATES.Indeterminate) {
      checkboxRef.current.checked = false;
      checkboxRef.current.indeterminate = true;
    }
  }, [value]);
 
  return (
    <label>
      <input ref={checkboxRef} type="checkbox" onChange={onChange} />
      {label}
    </label>
  );
};
Robin Wieruch
  • 14,900
  • 10
  • 82
  • 107