OnChange not fired in input field of litElement - javascript

I have following code:
export class ViTextfield extends LitElement
{
static get properties() {
return {
value: { type: String },
}
onChange(e) { console.log(e.target.value) }
render()
{
return html`
<div>
<div>
<input id="vi-input"
type="text"
value="${this.value}"
#change=${this.onChange} />
</div>
</div>
`
}
So everything is working fine for itself.
Now the developer who is using my component should be able to set the value thorugh the property e.g.
document.getElementById('myComponent').value = 1;
Now that brings 2 problems:
1) the value itself is not updated and 2) the onchange is not fired
Problem 1 I fixed with changing
value="${this.value}"
to
.value="${this.value}"
even I dont know why it is working (found this hack online).
But still the onChange is not firing...

The code doesn't work as you expect it to due to a couple of things:
Why does value not work when .value does?
lit-html uses the dot here to distinguish between assigning the value attribute or the property (value assigns the attribute and .value the property)
The easiest way of thinking about this is that attributes are those set on the HTML itself and properties are set to the Javascript object that represents that node.
Now, this is important in this case because the value property of an input element is only set from the attribute the when it's first rendered, if you want to change it later on you must set the property, not the attribute. Source
Why isn't the change event fired when the value property is changed from code?
This is because the change event is fired from the input only when the input's value changed due to some user input. Source
If you want to have some sort of side effect that gets fired not only when the user interacts when the input, but also when the property is modified in code, you probably want to use a setter. In your case that would look like this:
export class ViTextfield extends LitElement {
static get properties() {
return {
value: {
type: String
},
}
}
set value(value) {
const oldValue = this.value;
// do some side effect here
// set a pseudo-private property that will contain the actual value
this._value = value;
// call LitElement's requestUpdate so that a rerender is done if needed
this.requestUpdate('value', oldValue);
}
get value() {
// return the pseudo-private so that when vitextfield.value is accessed the correct value is returned
return this._value;
}
onChange(e) {
// update the property so that it keeps up with the input's current value
this.value = e.target.value;
}
render() {
return html `
<div>
<div>
<input id="vi-input"
type="text"
value="${this.value}"
#change=${this.onChange} />
</div>
</div>
`
}
}
For more info check this part of the LitElement guide

Related

Can access event.target, which has a value attribute, but event.target.value always returns undefined?

I'm new to React. This is absolutely baffling me. I can access the HTML element using event.target, and it shows value equal to some number, but every time I use event.target.value, I get undefined.
I've tried copying other code. I've tried Googling and searching Stack Overflow. I've tried using currentTarget instead of target and get the exact same results. This is extremely simple stuff; what is happening?.
class Calculator extends React.Component {
constructor() {
super();
this.state = {
input : "0"
}
this.handleNumber = this.handleNumber.bind(this);
}
handleNumber(event) {
event.preventDefault();
console.log(event.target);
console.log(event.target.value);
}
render() {
return (
<div id="calculatorLayout">
<div id="display">{this.state.input}</div>
<div className="calculatorButtons" id="zero" value="0" onClick={this.handleNumber}>0</div>
<div className="calculatorButtons" id="one" value="1" onClick={this.handleNumber}>1</div>
<div className="calculatorButtons" id="two" value="2" onClick={this.handleNumber}>2</div>
...etc.
I expect to get numbers when I access event.target.value, but every time I get undefined.
event.target returns:
<div class="calculatorButtons" id="zero" value="0">0</div>
event.target.value of the same element returns:
undefined
What am I doing wrong?
The .value property on DOM nodes is only for things like <input> or <textarea>. For other elements (like your <div>), you have to fetch the value via .getAttribute(). Thus
console.log(event.target.getAttribute("value"));
should do what you're asking.

React render same element which is dynamic component

I don't know how call this situation. It is following
I have a dynamic component which render a
<input type="text" placeholder={this.props.reduxStateValue} />
i.e.
function dynamicInput() {
var page = this.props.kindOfPage,
rState = this.props.reduxState;
if (page === 'foo') {
// foo input
// eg. rState.value === 10
return <input type="text" placeholder={rState.value}
onChange={this.handleFoo} />
} else {
// bar input
// eg. rState.value === 20
return <input type="text" placeholder={rState.value}
onChange={this.handleBar} />
}
}
The above example is very simple verion. The components has many different things for each this.props.kindOfPage.
The problem is that when the page === 'foo' and I insert 100 into the <input/> and switch to page !== foo, the 100 is in the <input/> of page !== foo.
i.e.
If I call the <input/> of page === 'foo' case as foo input and another as bar input,
insert 100 into foo input
switch the page to page !== foo then bar input is rendered
the 100 is in bar input
But the real value of each <input> has its own value.
i.e.
The foo input :
value : 100, placeholder : 10, shown value : 100
The bar input :
value : '', placeholder : 20, shown value : 100
What I want to do is that the bar input show its placeholder 20 since we do not insert the value 100 in bar input.
I think this problem is occurred because of VirtualDOM of react. The renderer just change its DOM when there is a different between each elements. Hence, whenever I change the attributes, the element is not changed and the value of input element also not changed.
But, I want to separate both and show different values. Is there any idea?
Perhaps you could approach this problem slightly differently by decoupling dynamic event binding from your component's render method to deal with this characteristic of React's rendering and DOM diffing.
Rather than conditionally render components with different event handlers as you currently are, why not opt for a more efficient render implementation (ie without conditional branching) by using a common event handler that dynamically invokes the appropriate event based on your kindOfPage state?
So for instance, you could follow this pattern (pseudo code):
function handleGeneric(event) {
var page = this.props.kindOfPage;
// Introduce dynamic branching in "generic" event handler, like so
switch(page) {
case 'foo':{
this.handleFoo(event)
break;
}
default:{
this.handleBar(event)
break;
}
}
}
function dynamicInput() {
var rState = this.props.reduxState;
// Simplyify your render method in this way, and delegate conditional
// branching to event handler
return <input type="text" placeholder={rState.value}
onChange={ event => this.handleGeneric(event) } />
}
Assuming I am right, I think you can simply add a key attribute to the input to fix this problem.
like this:
renderInput () {
var page = this.state.kindOfPage
var rState = this.state.reduxState;
if (page === 'foo') {
// foo input
// eg. rState.value === 10
return <input type="string" placeholder={rState.value1} key={"input1"}
onChange={this.handleBar} />
} else {
// bar input
// eg. rState.value === 20
console.log('test')
return <input type="string" placeholder={rState.value} key={"input2"}
onChange={this.handleFoo} />
}
}

State not updating inside of render

I'm trying to bind elements using setAttribute. It works, except it will not allow me to change the value.
Basically I want to pass a value from state as the value in the input.
Currently, the state does NOT update inside the render. It only takes the initial state. In the render, my 'console.log' only fires once.
The correct this.state.answer does appear in componentDidUpdate (and did Mount).
I have put this on JSFiddle https://jsfiddle.net/69z2wepo/91132/
class Hello extends React.Component {
cf = null
state = {answer:''}
componentDidMount(){
this.refs.q1.setAttribute('cf-questions', "How are you?")
this.cf = window.cf.ConversationalForm.startTheConversation({
formEl: this.refs.form,
context: document.getElementById("cf-context"), // <-- bind this to an element instead of html body
flowStepCallback: (dto, success, error) => {
// dto.text contains the value being passed to the form
// State appears in console.log
// dto.text = 'blah' + this.state.answer
// above ONLY passes 'blah'
success()
},
});
}
componentDidUpdate(props) {
this.refs.q1.setAttribute("value", this.state.answer);
}
onChange = e => {
this.setState({ answer: 'X' });
}
render() {
console.log('a change', this.state.answer)
// Only fires once
return (
<div>
<button onClick={this.onChange} className='but'> onChng </button>
<div id="cf-context" >
<form id="form" className="form" ref="form">
<select ref="q1" type="radio" id="links">
<option value="X">X</option>
<option value="Y">Y</option>
</select>
</form>
</div>
</div>
);
}
}
Most probably the issue is within the componentDidMount() method.
I am not sure about what you have been doing. But i guess you are setting the scope of the form Element within those particular div "cf-context".
So even after the state is updated, it takes value 'this' from particular scope which has been set. Try moving the scope even further up towards the top of DOM
I am not sure this is right. Please try

Issues with updating the State - React

I'm having issues in updating the state values, I'm rendering a external component using Map, and hence not able to access this. So on click of the component I'm not able to call the handleClick function to update the state values..
Here is the state :
this.state = {
attributes : {
hours : {
},
cost : 0,
amenities : defaultAmenities
},
primary_category : "General"
}
Where defaultAmenities is a external file with large javascript object.
The render function :
render() {
let basicAmenities, extendedAmenities
let basicAmenitiesList = [], extendedAmenitiesList = []
//Wrong way of storing this
let _this = this;
}
... More Logics / Switch Cases ...
let amenitiesList = basicAmenitiesList.map(function(item, index){
return <Attribute key={index} name={item.amenity_id} type={item.title} icon={item.icon} selected={item.isSelected} value="" onClick={_this.handleClick.bind(_this)}/>
})
And the attribute component
<div className="attribute-grid" onClick={this.props.onClick}>
...
</div>
Handle click is a function to setState on click of Attribute.
handleClick(e) {
console.log(e.target);
}
On click of the attribute, I need to update the state. The result of console log is attached below. I need to target the input values, but since it return the entire div, how do i get the values of name/value/placeholder?
<div class="attribute-grid-block" data-reactid=".0.2.0.3.0.1.$0.0"><div class="attribute-grid-img" data-reactid=".0.2.0.3.0.1.$0.0.0"><img src="petsIcon" data-reactid=".0.2.0.3.0.1.$0.0.0.0"></div><div class="attribute-grid-info" data-reactid=".0.2.0.3.0.1.$0.0.1"><h6 data-reactid=".0.2.0.3.0.1.$0.0.1.0">Pets</h6><input type="text" name="pets" placeholder="NO INFO FOUND" value="" disabled="" data-reactid=".0.2.0.3.0.1.$0.0.1.1"></div></div>
you can get what you need from the target. but you need to set the onClick on the element that you want it to be the target and then you will have it:
handleClick(e) {
const name = e.target.name;
const value = e.target.value;
const placeholder = e.target.placeholder;
console.log(placeholder);
}
if you want to set the onClick elsewhere you will need to send the values you want, so inside Attribute component you will have a function that will be invoke on click and call the this.props.onClick({ name: '', value: ''});
if you need to use this inside this function, and you are using react with classes. you can write this:
handleClick = (e) => {
console.log(this);
}

React - change input defaultValue by passing props

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.

Categories

Resources