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>
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>
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
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.
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.
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} />
)
}
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
}
/>
);
}
}
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}/>;
}
}
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>
);
};
Related
I'm working on this component and I want to validate its fields before every render. If they are not valid I want to disable a button in the parent.
Here is what I've got thus far:
export default function Input(props) {
const [inputs] = useState(props.inputs);
const didChangeRef = useRef([]);
useEffect(() => {
if (!_.isEqual(didMountRef.current, props.inputs)) {
validateInput(input);
didChangeRef.current = props.inputs;
}
});
const validateInput = input => {
const errors = useValidation(input);
if(Object.keys(errors).length !== 0) {
props.setIsValid(false);
}
}
return (
<input
onChange={e=> props.setProperty(e)}>
</input>
<input
onChange={e=> props.setProperty(e)}>
</input>
)
}
If an input is changed, it sets a property in the parent and this component is re-rendered. Inputs is an array of objects and I wish to validate it's contents on each render (or componentDidMount). I either manage to get it to loop infinitely or run the validation only once.
I appreciate your help.
P.S.:
I tried another approach as well, but it still ends up looping infinately:
export default function Input(props) {
const didMountRef = useRef(true);
useLayoutEffect(() => {
if (didMountRef.current) {
didMountRef.current = false;
return;
}
validate(input);
});
const validate = input => {
// validation...
}
}
Here is the parent component:
class CreateShoppingItem extends React.Component {
constructor(props) {
super(props);
this.state = {
storage: {},
isValid: true,
};
}
setIsValid = (isValid) => {
this.setState({ isValid });
};
render() {
return (
<div>
<Input setIsValid={this.setIsValid} inputs={this.storage.inputs} />
<Button disable={!isValid}></Button>
</div>
);
}
}
My input checkbox always returns true, when i mark it passsa true correctly, but when i unmark tb i get true. I would like the checkbox to return true when checked and false when unchecked, this way I would apply my logic in the handlesubmit function.
handleChange = e => {
const { name, value } = e.target;
console.log(name, value);
switch (name) {
case 'period': this.json.schedule = 'period'; break;
}
<input
type="checkbox"
name="period"
defaultValue
onChange={this.handleChange}
/>
Checkout the value of e.target.checked.
In your case, change this line: const { name, value } = e.target to include checked.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: true
}
}
handleChange = (e) => {
const { checked } = e.target
this.setState({
checked: checked
})
}
render() {
return (
<div>
<input type="checkbox"
onChange={e => this.handleChange(e)}
defaultChecked={this.state.checked}/>
{this.state.checked.toString()}
</div>
)
}
}
ReactDOM.render((<App/>), document.getElementById('testing'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="testing"></div>
You first need to define what check is considered? If it is check it is true and when it is not checked it is false. Here is some code to get you started.
{ this.state.data.map(function(item, index) { return (
<input type="checkbox" checked={item.value} onChange={this.handleChange.bind(this, index)}/>
);
}.bind(this))
}
You aren't checking wether the box is checked or not, try:
handleChange = e => {
if (document.getElementByClassName("period").checked) {
// box is checked
} else {
// box is unchecked
}
}
You can use useState.
import React, { useState } from 'react';
const App = () => {
const [Check, setCheck] = useState(false);
return (
<div>
<h1>{Check ? 'Checked' : 'Not checked'}</h1>
<input type="checkbox" onChange={(e) => setCheck(e.target.checked) }/>
</div>
);
};
export default App;
I'm new to React and I want to set a value for Radio button, however after I check one of them, then it's not checked after I set the state for a property of an object.
Here is my code:
import React, {Component} from 'react';
class TestComponent extends Component {
constructor(props) {
super(props);
this.completeChange = this.completeChange.bind(this);
this.state = {
hero: {}
};
}
getHero() {
}
completeChange(event) {
var hero = { ...this.state.hero
};
hero.Complete = event.target.value;
this.setState({
hero: hero
}, () => {
//when the callback is called, the radio button is not checked
console.log(this.state.hero.Complete);
});
}
render() {
return (
<div className="complete">
<label>Choose one: </label>
<input type="radio" id="Complete" value="true" onChange={this.completeChange}
value='true' checked={this.state.hero.Complete == true} />Yes
<input type="radio" id="Complete" value="false" onChange={this.completeChange}
value='false' checked={this.state.hero.Complete == false} />No
</div>
);
}
}
export default TestComponent;
What is wrong here? Can anyone here solve my problem?
Thank you in advanced.
You are mutating the state.
Try it this way:
completeChange(event) {
const {hero} = this.state;
const nextHero = {
...hero,
Complete: true
}
this.setState({ hero: nextHero }, () => {
//when the callback is called, the radio button is not checked
console.log(this.state.hero.Complete);
});
}
If you want to toggle the state then i suggest using the function version of setState, because state updates are async and when ever your next state relies on previous state you should use the function argument of setState to ensure you get the most recent state.
Something like this:
completeChange(event) {
this.setState(state => {
const { hero } = state;
const nextHero = {
...hero,
Complete: !hero.Complete
};
return {
hero: nextHero
};
});
}
And if you want to set the state based on the value of the button that was clicked you can check event.target.value:
completeChange(event) {
const { hero } = this.state;
const isComplete = event.target.value === 'true';
const nextHero = {
...hero,
Complete: isComplete
};
this.setState({hero: nextHero});
}
Here is a running example of your code:
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.completeChange = this.completeChange.bind(this);
this.state = {
hero: {}
};
}
getHero() {}
completeChange(event) {
const { hero } = this.state;
const isComplete = event.target.value === 'true';
const nextHero = {
...hero,
Complete: isComplete
};
this.setState({hero: nextHero});
}
render() {
return (
<div className="complete">
<label>Choose one: </label>
<input
type="radio"
name="Complete"
value="true"
onChange={this.completeChange}
checked={this.state.hero.Complete == true}
/>Yes
<input
type="radio"
name="Complete"
value="false"
onChange={this.completeChange}
checked={this.state.hero.Complete == false}
/>No
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<TestComponent />, rootElement);
<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"/>
By the way
Never do == against a Boolean:
this.state.hero.Complete == true
Its better to use the === instead in this case:
this.state.hero.Complete === true
I wrote a post about that if you're interested in more details.
You could directly check your e.target.value=='true' and set into your state like this.
You can check here is working stackblitz demo.
Code Snippet
class TestComponent extends Component {
constructor(props) {
super(props);
this.completeChange = this.completeChange.bind(this);
this.state = {
hero: {}
};
}
getHero() {
}
completeChange(event) {
this.setState({
hero: event.target.value == 'true'
}, () => {
//when the callback is called, the radio button is not checked
console.log(this.state.hero);
});
}
render() {
return (
<div className="complete">
<label>Choose one: </label>
<input type="radio" onChange={this.completeChange}
value={true} checked={this.state.hero} />Yes
<input type="radio" value={false} onChange={this.completeChange} checked={!this.state.hero} />No
</div>
);
}
}
render(<TestComponent />, document.getElementById('root'));
I need to always intercept when React unmounts a Component, no matter if that is a Functional or Class based component.
Here is my case:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
class Comp extends React.Component {
render() {
return (<h1>Hello World</h1>);
}
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
Now, as you can easily see, I am able to hook on every time <Comp /> gets unmounted. That is just what I need.
Things will dramatically change when that <Comp /> is a functional component:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
function Comp() {
return (<h1>Hello World</h1>);
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
So, my question is:
How can I hook on functional components?
I can change approach (or use React internal Apis), I just need to always intercept changes on a component passed as arguments for observe.
You can't. Functional components don't have lifecycles (yet).
Instead of messing with the functional component directly, why don't you just wrap the functional component in a class with a HOC. You could use recompose toClass for this.
function observe(component) => {
const classComponent = toClass(component):
const p = classComponent.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return classComponent;
}
Or just copy the code from here.
I have a class component which renders an input and a tooltip and controls state. The <SelectInputType> components job is to take a single prop of 'type' and render either a text input, a textarea, a select input or a checkbox group component. There are a load of props that need to be passed through this SelectInputType component, some of which are relevant to all 4 of the input components (placeholderText and required for example) and some of which are specific to a particular input (options for example is only relevant to checkboxes and select inputs).
render() {
const inputProps = {};
inputProps.placeholderText = this.props.placeholderText;
inputProps.required = this.props.required;
inputProps.class = this.props.class;
inputProps.value = this.props.value;
inputProps.options = this.props.options;
inputProps.updateText = this.handleInput;
inputProps.handleFocus = this.focusIn;
inputProps.handleBlur = this.focusOut;
inputProps.handleCheckboxChange = e => this.addCheckboxToList(e);
inputProps.closeCheckboxSelector = this.closeCheckboxSelector;
inputProps.readableSelectedCheckboxes = this.state.readableSelectedCheckboxes;
const inputClass = classNames('input-with-tooltip', {
focus: this.state.focus,
'step-complete': this.state.completed,
});
return (
<div className={inputClass}>
<InputTooltip
tooltipText={this.props.tooltipText}
completed={this.state.completed}
/>
<div className="drill-creation-input">
<SelectInputType type={this.props.type} {...inputProps} />
</div>
</div>
);
}
My SelectInputType component looks like this...
const SelectInputType = (props) => {
let component;
if (props.type === 'title') {
component = <TextInput />;
} else if (props.type === 'text') {
component = <TextareaInput />;
} else if (props.type === 'checkbox') {
component = <CheckboxInput />;
} else if (props.type === 'select') {
component = <SelectInput />;
}
return (
<div>
{component}
// Need to pass props through to this?
</div>
);
};
I am using the JSX spread attribute to pass the props down to the SelectInputType component, but I don't know how to then pass these on to the 4 input components (and if I do pass them on will I have errors with certain props not being valid on particular components?)
You could alternatively save the component type in the variable, not the component itself:
const SelectInputType = (props) => {
let ComponentType;
if (props.type === 'title') {
ComponentType = TextInput;
} else if (props.type === 'text') {
ComponentType = TextareaInput;
} else if (props.type === 'checkbox') {
ComponentType = CheckboxInput;
} else if (props.type === 'select') {
ComponentType = SelectInput;
}
return (
<div>
<ComponentType {..props} />
</div>
);
};
You might get errors. If so you could create utility functions to extract the props you need per input type:
extractTextInputProps(props) {
const { value, className, required } = props
return { value, className, required }
}
extractSelectInputProps(props) {
const { value, handleBlur, updateText } = props
return { value, handleBlur, updateText }
}
You could probably extract a more generic function out of this so you don't have to repeat the property name twice.
Then use them when creating your components with the spread operator:
let component;
if (props.type === 'title') {
component = <TextInput { ...extractTextInputProps(props) } />;
} else if (props.type === 'text') {
component = <TextareaInput />;
} else if (props.type === 'checkbox') {
component = <CheckboxInput />;
} else if (props.type === 'select') {
component = <SelectInput { ...extractSelectInputProps(props) } />;
}