React - state is inconsistent - javascript

What I'm trying to achieve: Pass data from child to parent.
How I'm trying to achieve it: Using this.state as described here
Not sure how to word the title: When I console.log(this.state) in the function in which I modify the state, the correct values in this.state are printed out. However, when I try to read the state in another function, this.state is apparently still at empty values.
constructor(props) {
super(props);
this.state = {
titleInputValue: "",
};
}
<form onSubmit={this.handleSubmit.bind(this)}>
<TextInput
val={this.state.titleInputValue}
changeHandler={this.textInputChangeHandler} />
</form>
// This is the function which can apparently only get the initial state
// title and body are empty strings, they should have values
handleSubmit(event) {
event.preventDefault();
const title = this.state.titleInputValue;
const body = this.state.bodyInputValue;
console.log(this.state);
Meteor.call('stories.insert',title,body);
}
// However, here the state is retrieved just fine.
// This is the function the child calls to update the state.
textInputChangeHandler(event) {
// This console.log call shows the correct state!
console.log(this.state);
this.setState({
titleInputValue: event.target.value,
});
}
TextInput has attribute onChange={this.props.changeHandler.bind(this)}
For illustration:
I wrote asd, and the state was successfully retrieved in textInputChangeHandler, which is the first two console.log calls, but then it's empty in handleSubmit.

This is because the event handler scope is not Component class level. When your component handles the event, it's context is the component (in your case TextInput ) not the parent.
You have to bind that function to this of Component class scope:
<TextInput
val={this.state.titleInputValue}
changeHandler={this.textInputChangeHandler.bind(this) } />
Using JavaScript bind you can specify the function context as well.

Related

react select onChange returning previous value instead of current

I'm attempting to update the state of a Parent component after the user updates the value of a Child component's select element.
Whilst I've got it somewhat working, I've noticed that when I fire the onChange event on my select, it will always return the previous value instead of the value that has just been selected.
class Parent extends Component{
constructor(props){
super(props);
this.state = { data: {
condition: "any"
}};
}
update = data => {
this.setState({ data: {
...this.state.data,
...data
}});
// This gets called every time I change the value of my select element.
console.log(this.state.data);
}
render(){
return (
<Child
condition={this.state.data.condition}
onUpdate={data => this.update(data)} />
);
}
}
class Child extends Component{
updateParent = data => {
this.props.onUpdate(data);
}
render(){
const options = [
["any", "Any Condition"],
["new", "Brand New"],
["used", "Used"]
];
return (
<select
defaultValue={ props.selected }
onChange={({ target }) => this.updateParent({ condition: target.value })}>
{ options.map(([id, name]) => (
<option key={id} value={id}>{ name }</option>
)}
</select>
);
}
}
In this example, if I select used, the console will return any. Then, if I select new, the console will return used.
setState operations are async in nature. So whenever a setState op is done, its not guaranteed that the updated value of state will be available just after the setState
statement
From React Doc
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
Now, if you want to use the new state value, you should store the value, in this case the data, in a variable, set your state, but use the variable to perform other operation inside the function, like calling API,etc.
Edit (As pointed out by #Grover):
setState also provides a second argument which is a callback that gets fired after the update operation takes place. One can get the updated state value in it and can use this to perform operations with the updated values.
this.setState({foo: 'bar'}, () => {
// actions
});
However, the React Doc suggests using componentDidUpdate instead of setState callback. This answer tries to explain it why: What is the advantage of using componentDidUpdate over the setState callback?

Pass value from class method to a react component

Situation: I have a class with its own methods. This class is instantiated in a React component.
What I'm needing: During one of the methods in the class, it changes the value of an input (this.$el) using .val(), but I'm listening to changes to this input in the React.component via onChange. I need to pass the value I'm using to set the value of the input (via this.$el.val(value)) to the React component to change its state.
What I've tried: I've tried chaining .change() and trigger('change') to the val(value), but it doesn't have any affect.
So, I need to be able to access the value I'm using in .val(value) in my React component WHEN it is set in the class method. I thought about using a method and calling that method on componentWillUpdate, but the component doesn't update since setting the input value via val() doesn't trigger a change.
Any ideas?
Component code:
// Create a ref to manage blur/focus state on the search input
this.inputRef = React.createRef()
// Setup initial state
this.state = {
supersearchResults: [],
value: this.props.initialValue || ''
}
this.onInputChange = this.onInputChange.bind(this)
tag('input', {
autoComplete: 'off',
className: blockElement('search-input'),
onChange: this.onInputChange,
placeholder: 'Find people, collections and pages',
ref: this.inputRef,
type: 'text',
value: this.state.value
})
Class code:
this = class
this.$el = input
// What is happening:
// A user types in an input, suggestions display in a list, when you
// select a suggestion, it calls the below to change the input value
this.$el.val(complete)
this.$el.blur()
this.hide()
If I understand correctly, you want to be able to access the value of a html field. Then please take into consideration the following considerations. Use controlled inputs such that the
class ReactComponent extends...
constuctor (props) {
super();
this.state = { fieldValue: props.fieldValue || '' };
}
onFieldChange = (event) => {
this.setState('fieldValue': event.currentTarget.value)
}
render () {
return (
<div>
<input type="text"
value={this.state.fieldValue}
onChange={this.onFieldChange}
>
<div>
)
}
}
Now having this code, in case you need to use some external class to call some code, simply put it correctly in the lifecycle. But in order to reference the value use the components state. And in case you want to programmatically want to change the value, do the same update the value in the state. If I missed something let me know in the comments.
You need to keep the state in your class component. consider the following
class TextExample extends Component{
constructor(){
super(props);
this.state ={
username: null
}
this._handleChange = this._handleChange.bind(this);
}
_handleChange(e){
const { name, value } = e.target;
this.setState({ username: value}) // for single TextField
// if you want to reuse this _handleChange function for all the TextFields then you need to use the below commented code which updates state of current TextField
//this.setState({ [name]: value }) // it is similar like the JSON bracket notation
}
render(){
return(
<div>
<TextField
id="username"
label="Username"
name="username"
value={this.state.username}
onChange={this._handleChange} // it will call the _handleChange function on every keypress inside the TextField.
/>
</div>
)
}
}

sync state with props to achive two way binding for form input in reactjs

i have a very long form (75 input to be exact), since i'm using redux to manage my application state, whenever i want to edit this form i want to setState of form to the prop to allow editing.
Example Code:
class VisitCard extends Component {
constructor(props) {
super(props); //props.visit = {name:'randome name', data:'etc..'}
this.state = Object.assign({},props.visit);
this.bindInput = this.bindInput.bind(this);
}
//BindInput will return props for Inputs, to achive two way binding
bindInput(config){
const {name,...props} = config;
return {
value : this.state[name],
onChange: event => this.setState({[name]:event.target.value}),
...props
}
}
render(){
return <div>
<input {...this.bindInput({name:'name', type:'text'})} />
<input {...this.bindInput({name:'data', type:'text'})} />
</div>
}
}
above code works perfect, problem is when this component mounts, it give me error "Cannot update during an existing state transition"
also sometimes if the value is not predefined in the props, the value for input will be undefined, so after props load from server and updates component i get another error "trying to change input from uncontrolled to controled" that is because this.state[name] was undefined then i got a value
so what am'i doing wrong ? how can i link the state of component with props value and make sure that if props changed, state does change too, while at sametime, if state changes this does not affect props.
I hope modify your code to match the below logic will resolve your issues. Look for comments inside the code for explanations
class VisitCard extends Component {
constructor(props) {
super(props);
//set your state to have a key that holds your prop value.
this.state = { visit: props.visit };
this.bindInput = this.bindInput.bind(this);
}
componentWillReceiveProps(nextProps) {
//if your props is received after the component is mounted, then this function will update the state accordingly.
if(this.props.visit !== nextProps.visit) {
this.setState({visit: nextProps.visit});
}
}
bindInput(config){
const {name,...props} = config;
// return defaultValue which you get from the props.
// you can add `value: this.state.visit[name]` to the below object only if you want your input to be controlled, else it can be ignored.
return {
defaultValue : this.props.visit[name],
onChange: event => this.setState(
{visit: { ...this.state.visit,
[name]:event.target.value}
}),
...props
}
}
render(){
// render empty if your props has not yet arrived.
if(!this.props.visit) {
return (<div />);
}
// render after you have values in props
return (<div>
<input {...this.bindInput({name:'name', type:'text'})} />
<input {...this.bindInput({name:'data', type:'text'})} />
</div>);
}
}

getInitialState() replacing existing state. How to pass existing state in React?

I'm making some buttons that will show a certain class depending on the status from the database.
I've passed my API results through an emitter and into my state using the below:
constructor(props) {
super(props);
this.state = {
tickets: [],
user: []
};
this.onGetAllTickets = this.onGetAllTickets.bind(this);
this.onGetCurrentUser = this.onGetCurrentUser.bind(this);
}
However, I'm having to set a new state in my button using the getInitialState() option. My button looks like this:
const AssignButton = React.createClass({
getInitialState() {
return {
isPressed: false,
completeStatus: "Assign"
};
},
handleClick() {
this.setState({
isPressed: true,
completeStatus: "Assigned",
});
},
render () {
var assignClass = 'ui orange right floated button assign';
//THIS IS WHERE I AM TRYING TO CHECK TICKET STATUS
if(this.state.tickets.status === 2) assignClass = 'ui green right floated button done disabled';
else if(this.state.isPressed) assignClass += ' disabled';
return <button className={assignClass} onClick={this.handleClick}>{this.state.completeStatus}</button>;
}
});
However, passing the this.state.tickets.status results in the following:
TicketeesBoard.js:123 Uncaught (in promise) TypeError: Cannot read property 'status' of undefined(…)
I'm assuming this is because my state is overwritten by the getInitialState() method.
The this.state.tickets.status etc. works outside of this AssignButton component.
How do I pass my tickets from the original state into the getInitialState() method?
The problem is that you're trying to access the state of a different component. Your'e addressing this.state.tickets.status where ticket state is not declared in AssignButton
You've got two components. TicketBoard & AssignButton.
Your setting the tickets state in TicketBoard and you're trying to access it in AssignButton
Try passing down the tickets state to the assignButton via props and the change your conditions from this.state.tickets.status to this.props.tickets.status.
In addition, make sure that you won't have problems with .status being called on an empty array...

Splice() method not works

I have some problem with splice() method in my React.js app.
So, this is an example app. Deletion not works now. What's wrong here? Part of code:
class CardList extends React.Component {
static propTypes = {
students: React.PropTypes.array.isRequired
};
// ADD DELETE FUNCTION
deletePerson(person) {
this.props.students.splice(this.props.students.indexOf(person), 1)
this.setState()
}
render() {
let that = this
return <div id='list'>
{this.props.students.map((person) => {
return <Card
onClick={that.deletePerson.bind(null, person)}
name={person.name}>
</Card>
})}
</div>
}
}
class Card extends React.Component {
render() {
return <div className='card'>
<p>{this.props.name}</p>
{/* ADD DELETE BUTTON */}
<button onClick={this.props.onClick}>Delete</button>
</div>
}
}
http://codepen.io/azat-io/pen/Vaxyjv
Your problem is that when you call
onClick={that.deletePerson.bind(null, person)}
You bind this value to null. So inside of your deletePerson function this is null instead of actual component. You should change it to
onClick={that.deletePerson.bind(this, person)}
And everything would work as expected =)
Changing the bind value to this will definitely cause the call to this.setState() to work, thus triggering the re-render, however I strongly recommend against the approach you've taken.
Props are supposed to be immutable. Instead use internal state and replace with new values rather than mutate them. To do this, set the state of your component in the constructor by doing something like:
constructor(props) {
super(props)
this.state = {
students: ...this.props.students
}
}
And now when you need to delete a person:
deletePerson(person) {
// notice the use of slice vs splice
var newStudents = this.props.students.slice(this.props.students.indexOf(person), 1)
this.setState({ students: newStudents })
}
And finally use this.state.students in your render method instead.
The reasoning behind this is that props are passed directly from the parent container component so modifying them wouldn't really make sense. To make more sense of my own code, I tend to pass in the prop named initialStudents and set my state to students: ...initialStudents to ensure I make the distinction between my prop variable and my state variable.

Categories

Resources