State not updating inside of render - javascript

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

Related

OnChange not fired in input field of litElement

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

React jsx version of html (using different tags, but same attribute and logic) isn't triggering jquery on('change') method

I'm migrating jQuery/javascript code into React piecemeal and for some reason I can't activate the "change" event when a value is changed React/Redux as it it's down in html. Does anyone know why this might be the case that the "change" event isn't being fired?
The original html code:
<div class="form-group row" style="margin-top: 10px!important;" >
<div class="col-md-2"><lang data-key="Resolution" /></div>
<div class="col-md-4">
<select class="form-control" id="screensize" style="width:100%;">
<option value="1920x1080" >1920 x 1080</option>
<option value="1680x1050">1680 x 1050</option>
<option value="1440x900">1440 x 900</option>
<option value="1280x800">1280 x 800</option>
<option value="1024x768">1024 x 768</option>
<option value="800x640" selected>800 x 640</option>
</select>
</div>
</div>
My react version of this.
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
...
settings: state.settings,
};
};
class DomStateWrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
...
<input
id="screensize"
value={this.props.settings.resolution} //** I have confirmed that this attribute gets its value from the redux store successfully
/>
...
</div>
)
}
}
export default connect(mapStateToProps)(DomStateWrapper);
The jQuery code I'm using to consume the change
$(document).on("change", "#screensize", function(e) {
var sizes = $(this)
.val()
.split("x");
if (showWin != null) showWin.resizeTo(sizes[0], sizes[1]); // resize the popup
});
Overall, the original html version of this code which uses , and sets its value and triggers the change method in the jquery code, but when I do it in react/redux, using , that .on() method is never triggered. I also used this website as reference for the "change" event.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
So getting a lot of flak for this question for having jQuery in React. I don't like it either, but it's not by choice when the task is to migrate from jQuery to React. Anyways, for anyone interested, the reason why this was happening is because it seems that the jQuery onChange event is being called either before or simultaneously with redux changing state. So to keep the jQuery change method while also updating state, the quick fix implemented was adding the action to the window:
window.reduxStore = store;
window.reduxActions = {resizeProjector}
So giving jQuery access to the redux action function resizeProjector, allowed us to update the jQuery code with:
$(document).on('change', '#screensize', function(e) {
var sizes = $(this)
.val()
.split('x');
reduxStore.dispatch(
reduxActions.resizeProjector({
width: sizes[0],
height: sizes[1],
}),
);
});
You should not use jQuery in your ReactJS app.
Do this instead:
<input
onChange={resizeWin}
value={this.props.settings.resolution} //** I have confirmed that this attribute gets its value from the redux store successfully
/>
const resizeWin = e => {
var sizes = e.target.value.split("x");
if (showWin != null) showWin.resizeTo(sizes[0], sizes[1]); // resize the popup
}

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);
}

how to add css classes to a component in react?

So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>

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