I have some form inputs and I'd like to be able to set some to be required and some to be optional. I'd like to use the required property so that the HTML is semantically correct:
<input onBlur={this.handleBlur} name="first_field" />
<input onBlur={this.handleBlur} name="first_field" required={false} />
<input onBlur={this.handleBlur} name="first_field" />
I'd prefer to add required={false} so I don't have to add the required prop to every field. However, checking for this then becomes somewhat strange:
handleBlur = (e) => {
if (e.target.value === '') {
// show an error if field empty and required
// NOT explicitly set to false
if (!this.props.required) {
setError('This field is required')
}
}
}
I can't do if (!this.props.required) because that would be true if the property isn't declared.
Is there a simpler way to handle this than the double check?
UPDATE: I hastily assumed this was a code issue, but after some discussion in comments below I now believe it is a semantic issue of how the HTML 'required' attribute should be used, OR how to handle this with React. I chose not to delete the question since there may be some value to this for others in future.
It sounds like you are trying to change the semantics of required. This attribute should default to false if it is not present. Explicitly setting required={false} is perfectly fine. However, changing the meaning when required is not present seems like a bad practice to me.
Instead, you can write your own component. You can name it OptionalInput for example. Give it a prop named optional and then have a render() function with something like this:
render() {
return <input name={this.props.name}
id={this.props.id}
...
required={!this.props.optional}/>
}
You will also need to add props for all input attributes which you want to support.
You can simplify it using defaultProps:
defaultProps: {
required: true
}
If the property is not defined, true will be used instead.
However, it might be better to invert the condition and call it optional, then you will be able to use it simply as:
<input name="first_field" optional />
The simplest way to approach this would be with a forced boolean casting. Any falsey values like undefined, null, 0, or false will result in false, any other value will be considered true. In this case, since required doesn't exist until it's specified as a prop, it will return false until assigned its value. You can try the following on your browser's terminal;
const data = {};
console.log('no required has been defined, ', !!data.required);
data.required = false;
console.log('required has been set to false, ', !!data.required);
data.required = true;
console.log('required should be true now, ', !!data.required);
delete data.required;
console.log('back to not defined, ' !!data.required);
Hope that helps!
Related
I'm trying to clarify some confusion I have about boolean props in React.
Suppose a have MyComponent with several boolean props prop1, prop2...
First: it seems that boolean props are like just others: you can define default values, either in defaultProps or in destructuring params:
const MyComponent = ({ prop1, prop2 }) => (
...
);
MyComponent.defaultProps = {
prop1: false,
prop2: true,
}
Or (equivalently... I think)
const MyComponent = ({ prop1 = false, prop2 = true }) => (
...
)
What's not clear is how to pass values. The natural "React style", again, seems to be
<MyComponent prop1={ BOOLEAN_EXPRESION1 } prop2={ BOOLEAN_EXPRESION2 } />
... including the static literals (false/true).
However, it's also stated that the correct (recommended?) way to pass boolean properties is presence/absence of the attribute, as in HTML5.
So that, instead of <MyComponent prop1={true} prop2={false} />, one should write <MyComponent prop1 />.
My questions are:
What is the correct way of passing boolean props? Are both acceptable?
In case HTML5 style is the recommended (or correct) way, how would one deal with dynamic values?
In case HTML5 style is acceptable, what about default values?
In the example above (where prop2 is true by default), if I write <MyComponent />, what value would prop2 get?
Edited: To the accepted answer, I'd like to add my own tip: to play along nicely with the HTML5 style, design your boolean props so that they are by default false. Put in other way: a boolean prop that defaults to true should be considered an antipattern.
Forenote:
Let's just think this differently and disregard rules established by HTML5 and focusing only on JSX. JSX has exactly two ways of passing true, <MyComponent prop /> and <MyComponent prop={true} /> and exactly one way of passing false <MyComponent prop={false} />. I do agree this is an oddity distinguishing between HTML5 and JSX, but JSX is a syntax extension to JavaScript and not HTML so it does not need to conform to any of the rules of HTML.
From the JSX docs:
This funny tag syntax is neither a string nor HTML.
FYI, all rules and behavior of JSX is listed in React's JSX docs, and it includes how defaulting a prop to true works. Important note: these docs do not inherit anything from HTML and shouldn't be compared with HTML specs either.
Answers:
What is the correct way of passing boolean props?
Passing an explicit true in JSX:
There are exactly two ways to pass an explicit true: passing true and defaulting a prop to true:
<MyComponent prop={true} />
<MyComponent prop />
Note: As stated in the docs, JSX's behavior of defaulting a prop to true is just an added feature that matches with HTML5's boolean attributes behavior.
Passing an explicit false in JSX:
There is exactly one way to pass an explicit false: passing false
<MyComponent prop={false} />
Note: This is where JSX's behavior differ from HTML5's boolean attributes behavior. There is not such thing as defaulting to false in JSX; it is only applicable for passing an explicit true. In contrast to HTML5, if you do not pass anything, you're really passing undefined, and your defined default values will be used instead. This is contrary to the HTML5 specs which would say it's false. Refer to the CodeSandbox link in the answer to #3 for this behavior.
Passing a boolean variable/expression in JSX:
Pass the variable or an expression to the prop:
// via variable
const foo = true;
<MyComponent prop={foo} />
const bar = false;
<MyComponent prop={bar} />
// via expression
<MyComponent prop={Math.random() > 0.5} />
Are both acceptable?
Referring to the way of passing an explicit true vs defaulting a prop to true, they are both acceptable in terms of compiling JSX. However, if you need consistency in a codebase for adhering to a certain style, add the ESLint rule jsx-boolean-value. I personally use the Airbnb JavaScript style which turns that rule on. The guide emphasizes on readability, and so it decided for omitting the explicit true.
In case HTML5 style is the recommended (or correct) way , how would one deal with dynamic values?
Do not use HTML5 style (defaulting props to true) for dynamic values (variables/expressions); use the React way of passing props (explicitly assigning prop values). See above for how to pass those.
Furthermore, in case HTML5 style is acceptable, what about the default values? In the example above (where prop1,prop2 are resp. false/true by default) what would give?
Here are the final values for prop1 and prop2 passed respectively:
MyComponent.defaultProps = {
prop1: false,
prop2: true,
}
<MyComponent prop1 />
// prop1 == true
// prop2 == true // defaulted to true
<MyComponent prop1={true} prop2={false} />
<MyComponent prop1 prop2={false} />
// prop1 == true
// prop2 == false
Here is the CodeSandbox link: https://codesandbox.io/s/elated-davinci-izut1?fontsize=14&hidenavigation=1&theme=dark
Note: Not adding an attribute and then not passing a value (such as prop2 in the first example) is the same as passing undefined unlike passing an explicit false for the second example. Therefore, prop2 got defaulted to true.
Let me tell you about passing boolean values
HTML5 Style
<MyComponent
prop2 // equivalent prop2={true}, works only for static true values
prop1={false} // You can't do something like that for false though
/>
// example
<Select
multiSelect // You won't be changing this value throughout the whole life cycle
/>
// They will however transpiled to
React.createElement(MyComponent, {prop1: false, props2: true}, null)
// So passing true isn't necessarily a must
Default Props
This method isn't any different for boolean than other types
<MyComponent
title={title} // typeof title = string | undefined | null
bold={bold} // typeof bold = boolean | undefined | null
/>
// In case title being undefined or null,
// React will take the defaultProps
defaultProps are equivalent to
static defaultProps = {
title: 'Hello World',
bold: true
}
props = {
title: props.title ?? 'Hello World',
bold: props.bold ?? true
}
// Or in function, it's much simpler and familiar
// Same as old C, if the props are undefined or null,
// default values will be taken, this is plain 'ol JS
const MyComponent = ({title = 'Hello World', bold = true}) => {
// boop
}
So to summarize and answer your question
What is the correct way of passing boolean props? Are both acceptable?
Yes, both are acceptable. React is un-opinionated, It depends upon which style you're following, AirBnB recommends you to use HTML5 style for passing static true values, while old TypeScript screams at you to change it to props2={true}.
In case, HTML5 style is the recommended (or correct) way, how would one deal with dynamic values?
HTML5 is applicable only for static true values. There's simply no way for you to use dynamic boolean values in HTML5 style.
Furthermore, in case HTML5 style is acceptable, what about the default values? In the example above (where prop2 is true by default), if I write <MyComponent />, what would happen?
<MyComponent /> will be transpiled to React.createElement(MyComponent, {}, null), meaning prop1 and prop2 will be undefined. Given that the default values for prop1 is false while prop2 is true. Their respective default values will be taken.
Depends on use case. Ever heard of separation of concerns? The same principle apply here. Handle it where it makes sense to handle. You can use variables in a component instead of defaultProps (the React way). If its a switch pass it down from parent.
I work on Chrome extension, i need to update lot of inputs of an html page made with React from numbers readed from CSV. I cannot update the web site.
-
An example of input copied from the rendered website :
<td><input class="input input_small fpInput" value="29,4"></td>
-
How it's made (not sure 100% about that, had to read the uglified js source)
{
key: "render",
value: function () {
return s.a.createElement("input", {
className: "input input_small fpInput",
value: this.state.value,
onChange: this.handleChange,
onBlur: this.handleSubmit,
onFocus: this.handleFocus
})
}
}
-
Each time you change the input value a function is called and a POST is made to save it.
I want to trigger the onBlur() or onChange() from my extension after i changed the input value to trigger the POST
I tried this :
var el = document. ... .querySelector('input'); // the selector is simplied of course
el.value = 321;
el.onChange(); // ERROR onChange is not a function
el.onchange(); // ERROR onchange is not a function
el.handleChange(); // ERROR handleChange is not a function
Any idea please ?
You can't call a React component's method directly from the DOM element it has rendered. You need to trigger an event that bubbles up so that React can catch it at the document level and process it normally, as it would do with a real one.
✨ Document.execCommand():
As pointed out by #woxxom in the comments, the easiest way to do that might be to focus the inputs and then use Document.execCommand():
const input1 = document.getElementById('input1');
const input2 = document.getElementById('input2');
input1.focus();
document.execCommand('insertText', false, 'input1');
input2.focus();
document.execCommand('insertText', false, 'input2');
<input id="input1" />
<input id="input2" />
⚒️ Manually Dispatching Events:
Otherwise, you might try manually dispatching a change, input and/or blur event using the Event() constructor in those fields after you change their value.
Also, note Event()'s second optional argument contains a field, bubbles, that is false by default. You need that one to be true. Otherwise, this won't work, as React is listening for events on the document.
Additionally, you might need to use Element.setAttribute() too, as that will update the value attribute on the DOM (the initial value on the field), while element.value will update the current value (and so the display value). Usually, though, it's not needed. For more on this see What is the difference between properties and attributes in HTML?.
This approach might have some timing issues you might need to handle using setTimeout when updating multiple inputs, so if the first one works, I'd go for that one instead.
const input1 = document.getElementById('input1');
// This updates the value displayed on the input, but not the DOM (you can do that with setAttribute,
// but it's not needed here):
input1.value = 'input1';
// Dispatch an "input" event. In some cases, "change" would also work:
input1.dispatchEvent(new Event('input', { bubbles: true }));
// It looks like only one field will be updated if both events are dispatched
// straight away, so you could use a setTimeout here:
setTimeout(() => {
const input2 = document.getElementById('input2');
input2.value = 'input2';
input2.dispatchEvent(new Event('input', { bubbles: true }));
});
<input id="input1" />
<input id="input2" />
To elaborate a bit more on #varoons answer, which is factually correct albeit a bit short on explanation.
You can do so by injecting (dispatching, in browser terms) the event into the dom:
// Needs setAttribute to work well with React and everything, just `.value` doesn't cut it
// Also changed it to a string, as all attributes are strings (even for <input type="number" />)
el.setAttribute("value", "321");
// As #wOxxOm pointed out, we need to pass `{ bubbles: true }` to the options,
// as React listens on the document element and not the individual input elements
el.dispatchEvent(new Event("change", { bubbles: true }));
el.dispatchEvent(new Event("blur", { bubbles: true }));
This will actually call all the listeners, even those made with React (as is the case in your de-uglyfied code ;)) or made with simple element.onChange = () => {...} listeners.
Example: https://codesandbox.io/s/kml7m2nn4r
el.dispatchEvent(new CustomEvent("change"))
I have an Input element that I want to display an error on when the form validation fails.
<Input ref="amount" error={false} />
When the user enters an incorrect amount, I want to change "error" to "true". How can this be done?
I have tried:
this.refs.amount.props.error = true;
Which seems bad but I'm not sure how else. If I add a conditional statement in the definition of the Input element, that seems to only evaluate once and then remain the same. Do I need to force an update on the element? If so, how?
Yes it's possible to validate the input when the form is submitted.
All you need is to keep track on input value and use same approach as #SajithDilshan for the input error.
this.state = {
error: false,
value: ''
}
...
render(){
return
...
<Input
ref="amount"
value={this.state.value}
error={this.state.error}
/>
...
}
Then onSubmit should looks like:
onSubmit(e){
const isError = this.state.value === '';
this.setState({error: isError});
// rest of your logic
}
Hope it will help!
Use the onChange() method on the input as below.
<Input ref="amount" onChange={this.onInputChange} error={this.state.error} />
After that implement the onInputChange() method as below in your component.
onInputChange = (e) => {
if (e.target.value === "") { // logic to validate the input
this.setState({error: true});
} else {
this.setState({error: false});
}
}
Note that this will add error property to the state.
Further, you should not modify the props within a component. Props are passes from parent component to the child component as immutable inputs.
This is not exactly the answer, but still:
This type of fiddling with each possible state of form element (valid, invalid, warning, show tooltip, was edited, in focus, left focus, was submitted, submit failed or not, etc) becomes to much trouble when the form grows beyond 1 input field.
I would suggest to use redux-form package that integrates with semantic-ui-react` almost perfectly and provided you have provided it with the validate function does everything else for you. It takes some time to understand the basics of it, but it really pays.
I have a requirement wherein I need to create fields dynamically through JSON.
I have created my components as required and the page is rendered with required fields.
But the fields are in non editable state, even though I had a onchange function in InputField js file. Following is the part of the code from my component
onChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
return (
this.props.screenField.fieldType === 'INPUT'
? <div className='form-group'>
<label htmlFor={this.props.screenField.name}>{this.props.screenField.label}:</label>
<input type={this.props.screenField.dataType === 'BOOLEAN'
? 'checkbox'
: this.props.screenField.dataType}
name={this.props.screenField.name}
id={this.props.screenField.name}
placeholder={this.props.screenField.helpText}
required={this.props.screenField.isRequired}
value={this.props.screenField.value} className='form-control'
onChange={this.onChange.bind(this)}/>
</div>
: null
)
}
Please find the URL below of the entire code.
https://github.com/yvsivateja/ReactJSApplications/tree/one/React-json-render
The React philosophy is that props should be immutable and to use the state if you need to change something.
Bind the value to the state not props. In the constructor set the initial state:
constructor(props) {
super(props);
this.state = {[this.props.screenField.name] : this.props.screenField.value};
}
bind to the state:
value={this.state[this.props.screenField.name]} className='form-control'
You might want to move all the logic out of the render function as that is usually good practice. The render function should only contain the components defined in a declarative way, just like HTML.
One thing you seem to be missing is the braces that are used to evaluate an expression inside JSX. Whatever you have in the return statement right now should be enclosed in {}:
{this.props.... === 'INPUT' ? ... : ...}
As I mentioned before, think about moving that outside render. Maybe you could use an if statement before the return. Another option is extracting the div into its own component (or just return it from a helper function).
I am having a very annoying issue with React and checkboxes. The application I am working with requires a list of checkboxes that represent settings that are persisted in the back-end. There is an option to restore the settings to their original state.
At first, I created a component that has an object like a map of settings. Each setting has a key and a boolean value. Hence:
{
bubbles: true,
gregory: false
}
Is to be represented as:
<input type="checkbox" value="bubbles" checked="checked" />
<input type="checkbox" value="gregory" />
Now, it seems React is ignorant about how a checkbox is supposed to work. I don't want to set the checkboxes' values, I want the "checked" property.
Yet, if I try something like this:
<input
type="checkbox"
value={setting}
checked={this.settings[setting]}
onChange={this.onChangeAction.bind(this)}
/>
I get this warning:
Warning: AwesomeComponent is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: [some useless docs page I read several times to no avail]
So I decided to create another component to wrap each individual checkbox and I got this:
<input
type="checkbox"
checked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Now the checked is a property present directly in my state.
This yields the same warning, so I tried using defaultChecked:
<input
type="checkbox"
defaultChecked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Which makes the warning disappear, but now it is unable to reset the checked value to the default one. So I tried playing with the method componentWillReceiveProps, this way I am quite sure my state is correct, this.state.checked is correct and the component renders again.
And it does. But the checkbox remains as it was originally.
For now I left that ugly warning and I am using checked.
How do I fix this thing so the warning goes away?
I was thinking that perhaps there is a way to force-re-render the component, so it captures the new defaultChecked value and uses it. But I don't know how to do that. Perhaps suppress the warning only for this component? Is that possible? Perhaps there is something else that can be done?
The problem arises if you set the checked property of your checkbox to null or undefined.
Those are "falsy" values in JS. However, React treats a value of null as if the property was not set at all. Since the default state of a checkbox is unchecked, everything will work fine though. If you then set checked to true, React thinks the property suddenly comes into existence! This is when React figures you switched from uncontrolled to controlled, since now the prop checked exists.
In your example, you can get rid of this warning by changing checked={this.settings[setting]} to checked={!!this.settings[setting]}. Notice the double bang (!!). They will convert null or undefined to false (and leave true alone), so React will register your checked property with a value of false and start off with a controlled form component.
I had this problem too and I, too, read the docs about controlled-components several times to no avail, but I finally figured it out, so I thought I'd share. Also, since version 15.2.0, normal inputs are triggered to be controlled by setting value, while checkboxes are initialized as controlled by setting checked, regardless of their value property, which added a bit to the confusion.
Amoebe's answer is correct, but I think there's a cleaner solution than the double bank (!!). Simply add a defaultProps property with value false for checked prop of your Checkbox component:
import React from 'react';
const Checkbox = ({checked}) => (
<div>
<input type="checkbox" checked={checked} />
</div>
);
Checkbox.defaultProps = {
checked: false
};
export default Checkbox;
Basically, the defaultChecked means you don't want to control the input – it just renders with this value and then there is no way to control it. Also, value shouldn't be used, but checked instead, so your second code should be correct. And you shouldn't use them both simultaneously.
<input
type="checkbox"
checked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Can you create a small fiddle with this behaviour?
Here is an answer using hooks should you choose to convert the class component to a functional one...
export default Checklist = () => {
const [listOfItems, setListOfItems] = useState([
{name: 'bubbles', isChecked: true},
{name: 'gregory', isChecked: false}
]);
const updateListOfItems = (itemIndex, isChecked) => {
const updatedListOfItems = [...listOfItems];
updatedListOfItems[itemIndex].isChecked = isChecked;
setListOfItems(updatedListOfItems);
}
return (
{listOfitems.map((item, index) =>
<index key={index} type="checkbox" checked={item.isChecked} onChange={() => updateListOfItems(index, !item.isChecked)} />
)}
)
}
You can assign your data to the state and then make use of the checked property associated with the individual checkbox to set the state as
{ this.state.data.map(function(item, index) {
return (
<input type="checkbox" checked={item.value} onChange={this.handleChange.bind(this, index)}/>
);
}.bind(this))
}
Sample Data in state is as
data: [{name:'bubbles', value: true}, {name:'gregory', value: false}]
JSFIDDLE
What finally worked, combining other answers:
const [categories, setCategories] = useState(
[
{
field: 'Label 1',
checked: true
},
{
field: 'Label 2',
checked: false
},
{
field: 'Label 3',
checked: false
}
]
)
const updateList = (item, isChecked) => {
const updatedList = [...categories];
updatedList[item].checked = isChecked;
setCategories(updatedList);
}
... in your markup:
{
categories.map((item, index) =>
<div key = {item.field}>
<label>
<span>{item.field}</span>
<input key={index}
type="checkbox"
checked={item.checked}
onChange={() => updateList(index, !item.checked)}
/>
</label>
</div>
)
}