I have the following code in my component. It will get called when I update certain things, thereby replacing a bunch of things in the UI. Everything is updating EXCEPT the value of the input as seen by the user.
let input = {
id: 'discount-' + geo + '-' + segment,
value: percentage,
disabled: applyToAll,
placeholder: '0.00'
};
cells.push(
<td key={'cell-' + geo + '-' + segment} className="discount-segment cfix">
<Text {...input} />
</td>
);
This is what <Text> returns, with things removed for clarity
return (
<div className={containerClasses.join(' ')} id={'field-container-' + id}>
{label}
<input
autoComplete="off"
type="text"
id={id}
ref="input"
{...extraProps}
name={id}
className={classes.join(' ')}
defaultValue={this.props.value}
onChange={this.props.onChange}
/>
</div>
);
Everything renders fine. Let's say the percentage value is 5 on start, it will show 5 in the field. I then do something else that updates percentage to 50. (A console log will show the right number on re-render). However the value is only showing 5 in the UI still. I am using defaultValue on the input field, but I figure that should be changing as the whole thing re-renders from parent.
Edit
Updated <Text> to set value instead of defaultValue. However then I need to use state to update the value when user types. Then when I re-render, I'm sending in new props with proper value, but of course props isn't updated. Catch-22 for me.
You need to perform a couple of steps:
Your input needs to only use the value and onChange props, do not use defaultValue
Initialize your state using your props to set your default value
Update your state when your props change
So, for example:
const MyComponent = React.createClass({
propTypes: {
defaultInputValue: React.PropTypes.string
},
getInitialState() {
return {
inputValue: this.props.defaultInputValue
};
},
componentWillReceiveProps(nextProps) {
if (nextProps.defaultInputValue !== this.props.inputValue) {
//Forcibly overwrite input value to new default if the default ever changes
this.setState({inputValue: nextProps.defaultInputValue});
}
},
render() {
return <input type="text"
value={this.state.inputValue}
onChange={e => this.setState({inputValue: e.target.value})} />;
}
});
In general initializing state off of props is a no-no. I would probably cringe a little bit if I saw this come across in a code review as there is probably some behavior that can be simplified.
You can also do:
<input value={this.state.inputValue || this.props.defaultInputValue} />
Such that the value of the input reverts to the prop value if you ever clear out the input. In this case you wouldn't have to forcibly overwrite the state with the new props.
componentWillReceiveProps is deprecated now, and even its successor getDerivedStateFromProps is recommended against. To square the circle of "I need to manage the state of a text input (state), but I also need it to update its initial value when I re-render (props)", the React blog recommended using a key in the props. When the key changes, the component remounts and props can initialize state anew.
To aggressively force the component to refresh whenever anything about the input changes, I would use
<Text {...input} key={JSON.stringify(input)}/>
Then you can use value={this.state.value} like a normal controlled form, but you can set this.state.value = props.value and it will change every time.
We can do it as this.We Must have to use onChnage() Event.
<input placeholder="as_you_want"
value={this.state.as_your_state}
onChange={e => {
this.setState({ as_your_state: e.target.value });
this.value = this.state.as_your_state;
}}
/>
Hope This Works.
Related
I´m using material ui and react grid from dev extreme to create a table with a input of type date, but when I try to type the date it doesn't recognize the value change until I get to the year value, which cleans the other values. Any Idea of what could be happening?
My Code:
const DateEditor = ({ value, onValueChange }) => {
return (
<TextField
value={value}
type="date"
onChange={event => {
onValueChange(event.target.value)
console.log(event.target.value)
}}
/>
);
};
const DateTypeProvider = React.memo(props => (
<DataTypeProvider
formatterComponent={DateFormatter}
editorComponent={DateEditor}
{...props}
/>
));
Nothing showing.
Showing when getting to year.
Deleting everything when I type next value.
Obs: It works perfectly when I select by the calendar.
just remove value props from the TextField
You should use event.originalTarget Instead of event.target Because originalTarget refers to the element the event is attached to.
target Can change for example for a click event: target Is the inner element that was clicked but not the base element
I have an array that creates a mapping of items with checkboxes. each item has a checked state:
const [checked, setChcked] = React.useState(false)
So the user checks say 5 out of the 20 checkboxes and then press a button (the button is in the higher component, where there is a mapping that creates this items with checkboxes) and it works as intended. But, after the button is pressed and the modal is closing, after I open the modal again, these 5 checkboxes are still checked. I want them to restart to be unchecked just like when I refresh and the state vanishes. Now, I am aware of techniques such as not saving state per each item and just saving the state of the array of items in the higher component but I am confused as I have heard that hooks were created so that it is good practice to sometime save state in dumb components.
Is there a simpler function to just restart to initial value?
Edit:
adding the code
<div>
{policyVersionItems.map(item=> (
<PolicyVersionItem
key={pv.version}
policyVersionNumber={item.version}
policyVersionId={item._id}
handleCheck={handleCheck}
>
{' '}
</PolicyVersionItem>
))}
</div>
And the item
const PolicyVersionItem: React.FunctionComponent<PolicyVersionItemProps> = props => {
const { , policyVersionNumber, policyVersionId, handleCheck } = props
const [checked, setChcked] = React.useState(false)
return (
<Wrapper>
<Label dark={isEnabled}> Version {policyVersionNumber}</Label>
<Checkbox
checked={checked}
onClick={() => {
if (isEnabled || checked) {
setChcked(!checked)
handleCheck(policyVersionId, !checked)
}
}}
/>
</Wrapper>
)
}
Some of it is not relevant. the handle check function is a function that returns data to the higher component from the lower component for example.
I'm having a heck of a time with Material UI's "Select" - about 10 hours into trying to get one working the way I'd like. I'd really appreciate some help.
This question is related to a previous one: Select MenuItem doesn't show when JSX saved to state and I suspect that if the "why" were answered on that, I might get a better idea of what's going on.
What I'm trying to accomplish is having a Select that does the following normal things:
has all the UI goodies (shows the question in the select spot, then
moves the question smaller and out of the way after you select a
non-null selection)
upon selecting something, the label shows up (as one would expect in
a drop down) rather than a blank (as I have been experiencing - check
previous question)
no warnings in the console about 'value' being undefined
when I click away from the select after selecting something, I don't
want the question label to move back on top of the answer like this:
I want a 'none' option that returns the select back to it's "empty"
form (That is to say, the question label shows at normal size in the
select)
I can set a selection to be selected by default
These shouldn't be hard tasks, but I can't for the life of me get it. It' rather embarrassing.
Then, upon selecting something, I want to save that selection (along
with the other selection options) to state (so I can save it to
localStorage so the larger form doesn't 'reset' upon page refresh)
Either way, I've currently got this JSX - effectively a cut-and-paste from the material ui demos with a map for the MenuItems:
<FormControl className={classes.formControl}>
<InputLabel htmlFor={this.props.label}>{this.props.label}</InputLabel>
<Select
value={this.state.selectLabel}
onChange={this.handleSelectChange}
inputProps={{
name: 'selectLabel',
id: this.props.label,
}}
>
{this.props.value.map(valueLabelPair =>
<MenuItem
key={this.props.XMLvalue + "_" + valueLabelPair.label}
value={valueLabelPair.value}
>
{valueLabelPair.label}
</MenuItem>
)}
</Select>
</FormControl>
the handleSelectChange looks like this -- again, exactly the same as the material UI demo.
handleSelectChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
This kind of works except the console gives me the following error:
Failed prop type: The prop value is marked as required in
SelectInput, but its value is undefined.
and the selected option and question label go on top of each other after you click away, like so:
Further, if I try to add in this code (in the componentDidMount function) with the goal of being able to pass in the 'selected'/default option...
componentDidMount() {
for (var i = 0; i < this.props.value.length; i++) {
if(this.props.value[i].selected) {
// *works* console.log("selected found: " + this.props.value[i].label);
this.setState({selectLabel:this.props.value[i].label});
}
}
}
it does not update the give me a default answer and also gives me the following additional error in the console (in addition to all issues above):
Warning: A component is changing an uncontrolled input of type hidden
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.
What am I missing?
Just define selectLabel into constructor:
constructor () {
super()
this.state = {
selectLabel:'',
}
}
I am unsure as to why the above solution did not work.
However, I rebuilt the Select to return "option" elements instead of "MenuItem" elements with the following function:
buildSelectOptions(optionsPairs) { // note, this references props and blank option could be split out for reuse
var JSX_return = [];
if (this.props.includeBlank && this.props.includeBlank === true) {
JSX_return.push(<option key="nada" value="" />);
}
for (var optionLabel in optionsPairs) {
JSX_return.push(<option key={optionLabel} value={optionsPairs[optionLabel]}>{optionLabel}</option>);
}
return JSX_return;
}
My render now looks like this:
<FormControl className={classes.formControl}>
<InputLabel htmlFor="age-native-simple">{this.props.label}</InputLabel>
<Select
native
value={this.state.value}
onChange={this.handleSelectChange('value')}
inputProps={{
name: this.props.label,
id: this.props.id,
}}
>
{this.buildSelectOptions(this.props.options)}
</Select>
<FormHelperText>{this.props.helperText}</FormHelperText>
</FormControl>
And the event handler looks like this:
handleSelectChange = name => event => { //FUTURE: combine the handlers (or split out question types to sub-components)
this.setState({ [name]: event.target.value },
() => this.props.stateChangeHandler(this)
);
};
the props passed to this object look like this:
{
"key": "test4",
"id": "test4",
"label": "Question Label 4",
"XMLValue": "XMLQ4",
"type": "DropDown",
"includeBlank": true,
"helperText": "PCodes FTW!",
"options": {
"No oe": "NA",
"My1": "One",
"My2": "Two",
"My3": "three"
},
"value": "One"
}
One of the key concepts for me was to learn that the value field on the Select element should be pointed at an item in this.state. Then, the onChange needs to pass the name of that state variable (which, confusingly, I have named 'value') to the eventHandler function.
The double arrow function header (curried function) of the handleSelectChange function still confuses me... (I don't understand how the 'event' property is getting there, given I'm calling this function with a single parameter)... but this works for now and I can try to refactor into syntax I am comfortable with (ie: function(a, b) { *do something* } ) at some future date. (yeah....)
Add class radio_label :
<FormControlLabel value="male" label="Male" control={<Radio />} className="radio_label"/>
Add css property :
.radio_label{
color:black
}
I am trying to create a custom event in one component and add an event listener in another component. The component that is listening for the event contains a function that I want to execute on the event. Below are what I have in the two components, I just feel like I'm going about this in the wrong way...
Component #1
toggleWidget() {
const event = new CustomEvent('sliderClicked', {
bubbles: true,
});
const sliderToggle = document.getElementById('input');
sliderToggle.dispatchEvent(event);
this.setState({
checked: !this.state.checked,
});
}
/* and then in my render... */
render() {
const displaySlider = this.state.isSliderDisplayed ? (
<div className="slider-container" >
<label className="switch" htmlFor="input">
<input type="checkbox" checked={this.state.checked} onChange={this.toggleWidget} id="input" />
<span className="slider round" />
</label>
<p className="batch-slider-title"> Batch Widget </p>
</div>) : null;`
Component Two
window.addEventListener('sliderClicked', this.refreshLayout);`
Any ideas as to what I may be doing wrong?
Basically it should work, but in react - if you rendered an element in a component you can use the ref to access it:
<input
type="checkbox"
checked={this.state.checked}
onChange={this.toggleWidget}
id="input"
ref={(c) => this.input = c}
/>
And your toggleWidget function should be something like this:
toggleWidget() {
...
this.input.dispatchEvent(event);
...
}
In React it's pretty common to pass down callbacks from parent to child.
const Child = ({handleClick}) => (
<div onClick={ handleClick } >Click me!</div>
);
const Parent = () => {
const childClickHandler = event => {
// do stuff
alert('My child is calling?');
}
return (
<Child handleClick={ childClickHandler }/>
);
};
Maybe that could work for you? You can try the code here. (JSFiddle)
Refs are generally considered something to avoid in React as they couple components together. see the documentation here:
https://facebook.github.io/react/docs/refs-and-the-dom.html
Your first inclination may be to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. See the Lifting State Up guide for examples of this.
Try using a global state container like redux and when you "toggleWidget" in one component, set a property in your redux store. Listen to that property by setting it as a prop in your second component(the one that you want to respond to a change/toggle). On change of that property your component will have the "componentWillReceiveProps" lifecycle method called and you can then have your "responding" component take whatever action you like.
Consider this example:
var Field = React.createClass({
render: function () {
// never renders new value...
return (
<div>
<input type="text" defaultValue={this.props.value || ''} />
</div>
);
}
});
var App = React.createClass({
getInitialState: function () {
return {value: 'Hello!'};
},
changeTo: function (str) {
this.setState({value: str});
},
render: function () {
return (
<div>
<Field value={this.state.value} />
<button onClick={this.changeTo.bind(null, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(null, void 0)}>Change to undefined</button>
</div>
);
}
});
React.render(
<App />,
document.getElementById('app')
);
I want to pass value into defaultValue as prop of dumb input component. However it never re-renders it.
As a previous answer mentioned, defaultValue only gets set on initial load for a form. After that, it won't get "naturally" updated because the intent was only to set an initial default value.
You can get around this if you need to by passing a key to the wrapper component, like on your Field or App component, though in more practical circumstances, it would probably be a form component. A good key would be a unique value for the resource being passed to the form - like the id stored in the database, for example.
In your simplified case, you could do this in your Field render:
<div key={this.props.value}>
<input type="text" defaultValue={this.props.value || ''} />
</div>
In a more complex form case, something like this might get what you want if for example, your onSubmit action submitted to an API but stayed on the same page:
const Form = ({item, onSubmit}) => {
return (
<form onSubmit={onSubmit} key={item.id}>
<label>
First Name
<input type="text" name="firstName" defaultValue={item.firstName} />
</label>
<label>
Last Name
<input type="text" name="lastName" defaultValue={item.lastName} />
</label>
<button>Submit!</button>
</form>
)
}
Form.defaultProps = {
item: {}
}
Form.propTypes = {
item: PropTypes.object,
onSubmit: PropTypes.func.isRequired
}
When using uncontrolled form inputs, we generally don't care about the values until after they are submitted, so that's why it's more ideal to only force a re-render when you really want to update the defaultValues (after submit, not on every change of the individual input).
If you're also editing the same form and fear the API response could come back with different values, you could provide a combined key of something like id plus timestamp.
defaultValue only works for the initial load. After that, it won't get updated. You need to maintain the state for you Field component:
var Field = React.createClass({
//transfer props to state on load
getInitialState: function () {
return {value: this.props.value};
},
//if the parent component updates the prop, force re-render
componentWillReceiveProps: function(nextProps) {
this.setState({value: nextProps.value});
},
//re-render when input changes
_handleChange: function (e){
this.setState({value: e.target.value});
},
render: function () {
// render based on state
return (
<div>
<input type="text" onChange={this._handleChange}
value={this.state.value || ''} />
</div>
);
}
});
I'm fairly certain this has to do with Controlled vs. Uncontrolled inputs.
If I understand correctly, since your <input> is Uncontrolled (doesn't define a value attribute), then the value will always resolve to the value that it is initialized with. In this case Hello!.
In order to overcome this issue, you can add a value attribute and set it during the onChange:
var Field = React.createClass({
render: function () {
// never renders new value...
return (
<div>
<input type="text" defaultValue={this.props.default || ''} value={this.props.value} />
</div>
);
}
});
Here is a plunker showing the change.
You can make the input conditionally and then every time you want to force an update of the defaultValue you just need to unmount the input and then immediately render it again.
The issue is here:
onClick={this.changeTo.bind(null, 'Whyyyy?')}
I'm curious why you bind to null.
You want to bind to 'this', so that changeTo will setState in THIS object.
Try this
<button onClick={this.changeTo.bind(this, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(this, void 0)}>Change to undefined</button>
In Javascript, when a function is called, its called in the scope where it was called from, not where it was written (I know, seems counter intuitive). To ensure it is called in the context you write it, you need to '.bind(this)'.
To learn more about binding and function scope, there are lots of online tutes, (some much better than others) - you might like this one: http://ryanmorr.com/understanding-scope-and-context-in-javascript/
I also recommend using the React Dev tools if you are using firefox or chrome, this way you would have been able to see that state.message was not changing:
https://facebook.github.io/react/blog/2015/09/02/new-react-developer-tools.html
Use conditional rendering, then the component will load correct initial value. Something like in this module:
class MenuHeaderInput extends React.Component{
constructor(props){
super(props);
this.handleBlur = this.handleBlur.bind (this);
}
handleBlur (e) {
this.props.menuHeaderUpdate(e.target.value);
}
render(){
if (this.props.menuHeader) {
return (
<div className="w3-row w3-margin" onClick = {() => this.props.handleTitleClick (10)}>
<div className="w3-third" ><pre></pre></div>
<input
className = {"w3-third w3-input w3-jumbo " + EDIT_COLOR}
type = "text"
defaultValue = {this.props.menuHeader}
onBlur = {this.handleBlur}
/>
<div className="w3-third" ><pre></pre></div>
</div>
)
}
else {
return null;
}
}
}
Related to Sia's excellent answer above: https://stackoverflow.com/a/41962233/4142459.
For my case I had a few ways in which a form could be updated:
users could input values into form fields
An API request allowed users to restore from previous versions
Users could navigate to a filled out form (using queryParams of the URL)
clearing the form fields.
Etc more ways of allowing all the fields or just a single change to happen from user action or websockets.
I found that the easiest way to make sure the state of the form is reflected in its inputs is indeed:
To provide a manually-controlled key prop on the top level of the form or parent element to the form (as long as it is above the inputs in the DOM tree.
When users are typing a key update does not need to happen.
I made the key be a simple formHistoricalVersion and as certain updates external to a user typing/selecting/etc interacting with the form field's values happened I incremented the formHistoricalVersion.
This made sure that the state of the form whether by user action or by API request was in-sync--I had complete control over it.
Other solutions I tried:
While making the API request make the whole form disappear (when loading change to a loading spinner instead of the form). Disadvantage to performance and for clearForm it was a bit crazy to do, but possible with setImmediate to convert the form to a loading spinner when they first clear it, then setting isLoading back to false in the setImmediate.
Adding a key on each input: this worked amazingly, but it had a weird blip whenever users would type so I had to get rid of it.
Putting a static key for the form (field.id) (as suggested by above answer) didn't cover all the use cases I had.
In conclusion, it worked pretty easily to set the key of the form with react/redux, I just would add the equivalent of:
return {
...state,
formFieldState: payload.formFields,
historicalFormVersion: state.historicalFormVersion + 1
}
This was necessary because I was using some 3rd party libraries and my own Numeric Input that took in value as a prop but used value as a defaultValue:
const NumberDisplay: FunctionComponent = ({ value, setValue }) => (
<input
defaultValue={convertToSpecialNumberDisplay(value)}
onBlur={(e) => convertToSpecialNumberDisplay(e.target.value)}
onFocus={(e) => convertToNumberFromDisplay(e.target.value)}
onChange={(e) => setValue(e.target.value)}
/>
)
Approximate Redux of overall Form:
const FullForm: FunctionComponent = () => {
const dispatch = useDispatch();
const formState = useState((state) => state.formState);
const formHistoricalVersion = useState((state) => state.formHistoricalVersion);
return (
<form key={formHistoricalVersion}>
{renderFormFields(formState, dispatch)}
</form>
)
}
I also face this problem, what I did was to manually update the input value when the props has change. Add this to your Field react class:
componentWillReceiveProps(nextProps){
if(nextProps.value != this.props.value) {
document.getElementById(<element_id>).value = nextProps.value
}
}
You just need to add an id attribute to your element so that it can be located.