I have a React Component that wraps an <input type='text' /> but only calls onChange when the value has changed to something valid. (Its own state allows it to change the text value of the input, but when it calls onChange, it actually returns an object of the parsed value of the input.)
export default class MyInputClass extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = { textValue: '' };
// ...
}
// ...
handleChange(e) {
// set the state regardless of value so the input's text changes
this.setState({ textValue: e.target.value});
// determine whether the value is valid, and if so, call onChange()
let myObj = MyParsedObject.parse(e.target.value);
if (myObj !== null && this.props.onChange) {
this.props.onChange(myObj);
}
}
render() {
return <input onChange={this.handleChange} value={this.state.textValue} />;
}
}
// ...
MyInputClass.propTypes = {
onChange: React.PropTypes.func
};
Right now I have a property onChange, which is a React.PropTypes.func, and when the input's change is triggered, I attempt to parse the input's value. If it's successful, I check to see if this.props.onChange is not null, and then call onChange(myParsedObject).
This works, but it doesn't feel right. Handlers of onChange should expect an Event parameter (or SyntheticEvent in React), not an object. Further, this pattern only allows one handler for onChange, whereas real Events can have multiple handlers.
How are you supposed to design React Components to emit real events?
If MyInputClass is designed to be a generic wrapper around inputs, it might make sense to call this.props.onChange with the event, rather than the parsed input, and let the parent component decide how to parse it. If, however, MyInputClass is a wrapper for a specific type of input, it might make sense to also pass on the parsed value. You could always do both, and make it an explicit part of the API:
this.props.onChange(e, myObj);
Or use onChange as the generic handler and onWhateverChange as the parsed version; for example, for a JsonInput component, you might do
this.props.onChange(e);
this.props.onJsonChange(parsedJson);
Related
Say one component always calls another, but adds some properties, do you propType for required properties in this component even if they are checked in the component it calls? Or only at the higher level?
Simple Example:
const Input = props => {
let finalProps = {
...props,
...{onChange: (e) => props.onChange(props.id, e.target.value)}
};
return <input {...finalProps}/>
};
Input.propTypes = {
id: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
tag: PropTypes.string
};
Input.defaultProps = {
value: ''
};
const Checkbox = props => <Input {...props} type="checkbox"/>;
Checkbox.propTypes = {
id: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
In this example, would you propType "Checkbox" as well as "Input" or just for "Input"?
My way of looking at it is, that if you use some props in a component, you have to propType it and define relevant defaultProps. But if you are not using any of the props, and you are just passing them down without even accessing them, i would not use propTypes or defaultProps.
In your case <Checkbox/> does not use any of the props passed to it, but just passes them down to <Input/> component. Hence you don't need to propType, id or onChange in <Checkbox/>, but in <Input/> you are using onChange and since you are rendering a <input/> tag, there are props you should supply to it, so you need to check for propTypes
It depends on all components which use the "Input" component. If all those higher-level components need to use the "type" prop, and I assume that they do, then I would put that property in the "Input" component, since it always appears and is always used.
Even if you have the situation when there are, for instance, 5 components which use the "Input" component, and 2 of those components work with a specific, but the same prop, I would put that prop, but without the "isRequired" attribute.
I have an input component that is largely just a presentation wrapper for the actual input field itself. The parent component should have control of the value and validation and the like.
Below is a simple sketch of the code and the two components:
TextInput.js
export default {
name: "TextInput",
props: {
value: [String, Number]
},
methods: {
inputted(e) {
this.$emit('input', e);
}
},
render() {
return (
<input
value={this.value}
onInput={(e) => this.inputted(e)}
/>
);
}
}
Example.js
export default {
data() {
return {
depositAmount: ""
};
},
methods: {
handleInput(newValue) {
let parts = newValue.match(/^(\d+)?(\.)?(\d{0,6})?$/);
if (parts !== null) {
this.depositAmount = parts[0];
}
},
},
render() {
return (
<TextInput
value={this.depositAmount}
onInput={(e) => this.handleInput(e.target.value)}
/>
);
}
}
It would seem to me that the lifecycle here should be roughly the following:
user enters a string
onInput fires on the base <input> tag
onInput fires on TextInput
handleInput is called and validates the full string against the regex
if the new input is valid, depositAmount is updated, if not nothing happens
the value prop on TextInput is set to depositAmount
the value of the base <input> tag is set to value from TextInput (which is depositAmount)
The docs make it seem as though this should be the case.
This is all fine and dandy when the input is valid. However, when it is not valid, depositAmount does not change but the value of the base <input> element does change.
How can I make the value of the input 1:1 with the parent's data, even when the input updates but the parent's data doesn't?
Working example. As you can see, the <input> that is disabled and simply taking the value of the validated and sanitized input via depositAmount works properly. But the <input> that is being interacted with and typed into ignores its controlled value and simply reflects what the user has entered.
PSEUDO-SOLUTION
The only way I've found to solve this is to add this.$forceUpdate() inside the root event handler
TextInput.js
...
methods: {
inputted(e) {
this.$emit('input', e);
this.$forceUpdate();
}
}
...
I say this is a "pseudo" solution because while it does solve the problem and forces the input to always take the value it's being fed from its parent, I'm left unsatisfied as I deplore using $forceUpdate when it feels like this should be a situation where "it just works."
So while there is a quick patch, I'm leaving this open as I'd love to hear an actual solution or hear what I'm not grasping here that is necessitating the $forceUpdate. Seems like Vue's diffing should notice that the prop is different from the actual value of the <input> element even though they should be bound.
I have a TextField that I only render when another radiobutton is clicked. According to the React and Material-UI documentation, I should be able to get a ref to an input element inside a Mui TextField using inputRef={this.myRef} on the TextField. I'm able to do this for another TextField that is not toggled, but when I try that the with TextField that I just turn on, it shows null.
I've tried using inputRef={this.otherText} and inputProps={{ref: this.otherText}} and same result.
// Start of my class where I do the createRef()
class Form extends Component {
constructor(props) {
super(props);
this.otherText = React.createRef();
}
// Start of function where I try to reference the ref:
processApplication = e => {
if (e.target.value.toLowerCase() === 'other') { // this triggers the TextField to be rendered
console.log(this.otherText); // Returns null, kinda - see screenshot
}
// The TextField I'm trying to reference:
<TextField
id='applicationOther'
name='applicationOther'
label='Describe application:'
margin='normal'
multiline={true}
fullWidth={true}
onChange={this.anyChange}
autoFocus={true}
inputRef={this.otherText} // Here's where the reference is made
/>
I expect this.otherText to have a reference to the element, but this.otherText.current is null.
So to add some content to an input of any tipe, including Material TextField, you'd assign it as a value, for instance:
this.state = { input: 'Some string' }
<TextField value={this.state.input} />
Keep in mind the slight difference between uncontrolled and controlled components, so depending on your use case, you may want to pass defaultValue instead of value. From the docs:
In the React rendering lifecycle, the value attribute on form elements will override the value in the DOM. With an uncontrolled component, you often want React to specify the initial value, but leave subsequent updates uncontrolled. To handle this case, you can specify a defaultValue attribute instead of value.
Docs link
I have had same issue, doing following worked for me:
<TextField
variant="filled"
inputRef={(input) => {
if(input != null) {
input.focus();
}
}}
/>
The trick here was if(input != null) in my case. You can also take a look at working example here: CodeSandBox- Material-ui-TextFieldFocus
I have a "Date input" component that contains a <input type="text">.
It's purpose is to allow users to type in a valid date (e.g., mm/dd/yyyy).
Once a user enters a valid date, the parent of the component should receive that date.
Based on this - I'm trying to figure out whether the component or the parent should manage state.
If I set it up so the parent manages state:
render() {
return (
<div>
<input type="text" value={this.props.value} onChange={this.props.handleChange} />
</div>
);
}
... this is no good, because the parent will be notified with every single change, and the prop will be set to all the "draft" values (e.g., "07/2") while the user is typing.
That suggests that I should set this up so that the component manages it's own state:
class InputDateWithLabel extends Component {
constructor(props) {
super(props);
this.state = {
value: formatDate(this.props.value) // formatDate() formats a date object into an 'mm/dd/yyyy' string
};
this.handleChange = this.handleChange.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
handleChange(event) {
// use setState() to update this.state.value so the <input> shows what the user types
}
handleBlur(event) {
// if the user entered a valid date,
// convert the user's input to a date object,
// and communicate that to the parent. E.g.,:
this.props.onChange([new date]);
// Otherwise, handle the "error"
}
render() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange} onBlur={this.handleBlur} />
);
}
}
This version works exactly the way I want it to, except for one more requirement...
Based on things that might happen elsewhere in my application, the parent may need to set the date in this component. However - now that the component is managing it's own state - when the parent changes props.value, my component will ignore it.
The React documents address this scenario here: You Probably Don't Need Derived State
But their solutions don't seem to apply:
One way to avoid the problems mentioned above is to remove state from our component entirely.
This is no good, because I don't want to make the parent responsible for validating the user's date input. My component should be self-contained, including the date validation, which means it needs to manage the "draft states" of the user's input.
Another alternative would be for our component to fully own the “draft” state. In that case, our component could still accept a prop for the initial value, but it would ignore subsequent changes to that prop
This is no good, because I need to retain the ability for the parent to change the value when appropriate.
The rest of the React documentation mentions a few other possibilities (getDerivedStateFromProps), but it goes to great lengths to stress that they're probably not correct. (Note the title of the article!)
This does not seem like an uncommon situation, so there must be a clear, simple, well-documented way to handle it, that's done the right "React-way". What is that?
Having a component manage it's own state doesn't seem that bad in your case, but you will need to add componentWillReceiveProps which adds another piece of code to manage.
componentWillReceiveProps(nextProps) {
this.setState({
value: formatDate(nextProps.value)
});
}
I have a simple TextInput for entering your name. I want to pass this to another scene. First, though, I want to save the value to the state of the current component. When I open up the debugger, I get undefined for every console.log(this.name) I have.
Here is my code:
constructor(props) {
super(props);
this.state = {
name: ''
};
}
<TextInput style={styles.inputName}
placeholder="Enter your name"
textAlign="center"
onChange={this.handleChange.bind(this)} />
handleChange(event) {
this.setState({
name: event.nativeEvent.text
});
console.log(this.name)
}
Do you know why I am always getting "undefined" as the value of name? What is the proper way to set the state equivalent to what is being typed?
Is a better approach to set state only after the submit button is pressed? If so how is that done?
You're attempting to access this.name, which is checking for a (likely nonexistent) name property on the component object in your case. You should be looking at this.state.name. However, even if you did attempt to log out the updated state in your event handler via this.state.name, you would probably still run into problems. From the React docs:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value.
Finally, I would suggest extracting the name value from your event through event.target.value rather than going through the nativeEvent property.