Discrepancies with setting state values in React - javascript

I'm starting out React JS and following this little exercise, https://facebook.github.io/react/docs/more-about-refs.html.
It's fairly simple, but I'm facing a huge discrepancy when setting the state value. When I set the state, I view it in the console by doing this: console.log("s: "+this.state.userInput);. And I also display it in the view with {this.state.userInput}. Fairly simple. But not really. The event and the state value always seem to be a letter apart in the console, but it is perfectly displayed in the view. How is that even possible?!
I wanted to query my server whenever the state changes, but the state is always a letter behind. It's so weird. Could someone please explain to me what this is? And how I can avoid it?
Here's the code.
var SearchContainer = React.createClass({
getInitialState: function() {
return {userInput: ''};
},
handleChange: function(e) {
console.log("e: "+e.target.value);
this.setState({userInput: e.target.value});
console.log("s: "+this.state.userInput);
},
clearAndFocusInput: function() {
this.setState({userInput: ''}); // Clear the input
// We wish to focus the <input /> now!
},
render: function() {
return (
<div>
<div onClick={this.clearAndFocusInput}>
{this.state.userInput}
</div>
<input
value={this.state.userInput}
onChange={this.handleChange}
/>
</div>
);
}
});
And this is the weird output,
Console:
View: (HTML Page)

It's because the state hasn't been updated yet. Even though you've explicitly set it using this.setState, it won't be set until the method has finished executing.
If you need the new value you could always use it from e.target.value.

By default, React components re-render when their state is changed.
Therefore, in order to get an accurate reading of the state at a given point, place your console statement inside of the render function, like so:
var SearchContainer = React.createClass({
getInitialState: function () {
return {
userInput: ''
};
},
handleChange: function(event) {
var value = event.target.value;
console.log('Value is ' + value);
this.setState({
userInput: value
});
},
clearAndFocusInput: function() {
this.setState({
userInput: ''
});
},
render: function() {
var userInput = this.state.userInput;
console.log('State is ' + userInput);
return (
<div>
<div onClick={this.clearAndFocusInput}>
{userInput}
</div>
<input
value={userInput}
onChange={this.handleChange}
/>
</div>
);
}
});

Related

React setting input's value

I've seen on many React tutorials regarding managing input values. The following pattern:
On Parent passing props to Input Component, handleInputText sets the state for anyValue:
<InputComponent textValue={this.state.anyValue} onInputtingText={this.handleInputText}/>
On Input Component, onEvent can be ==> onChange, onBlur...:
<input type='text' ref='inputRef' value={this.props.textValue} onEvent={this.handleInput}/>
InputComponent's handleInput:
handleInput(){
this.setState(this.refs.inputRef.value)
}
Now my findings, I try to log it when the parents function when setting the state and it logs the initial. This are some tentative conclusions:
Whenever an event its trigger the value of the input is not the current value of the InputComponent. It is the value set on the parent to that value.
Both the input's value and this.props.textValue match on the second triggering of the event.
My question is, how do you handle this the react way? Or do you have to check this inside the handleInput function?
Thanks in advance.
You can set state in this.handleInputText and call it inside InputComponent,
var App = React.createClass({
getInitialState() {
return { anyValue: '' };
},
handleInputtingText(value) {
this.setState({ anyValue: value });
},
render() {
return <div>
<p>{ this.state.anyValue }</p>
<InputComponent
textValue={ this.state.anyValue }
onInputtingText={ this.handleInputtingText }
/>
</div>
}
});
var InputComponent = React.createClass({
handleInput(e) {
this.props.onInputtingText(e.target.value);
},
render: function() {
return <input
type="text"
value={this.props.textValue}
onChange={ this.handleInput }
/>;
}
});
Example

React, Local cached variable and firing onclick on render

I am trying to make a local variable for each component instance, something like a little cache because it is storing information that toggles something and does not need to be on the state/store. So I have attempted it like so:
Setting a default prop to keep my info in :
getDefaultProps: function getDefaultProps() {
return {
showPreviewModal: { value: false,
writable: true
}
};
},
Setting a function to toggle this prop :
togglePreviewModal: function togglePreviewModal() {
this.props.showPreviewModal = !this.props.showPreviewModal;
},
And having that function fired by a click function
<button className="btn btn-default btn-blue previewAsset" onClick={() => this.togglePreviewModal() }>
I thought this would work in theory, but the onclick is firing immedietly on render. I googled this issue and it seems like the best result is to change the click function to :
{() => { this.props.togglePreviewModal() }}
However this does not work either, the click function is still firing immediately.
You can't directly mutate props like that - since props are explicitly passed down from a parent component, you'd need to change the prop at the source. Presumably, it originates from the state of a component somewhere up the hierarchy.
To do that, you'd pass down a function along with the prop that changes it at the source (using setState()). Here's an example:
var ParentComponent = React.createClass({
togglePreviewModal: function() {
this.setState({
showPreviewModal: !this.state.showPreviewModal
};
},
getInitialState: function() {
return {
// Unnecessary but providing for clarity
showPreviewModal: false
};
},
render: function() {
// This is for whatever values you were mapping over
var childComponents = ...map(function() {
return <ChildComponent togglePreviewModal={this.togglePreviewModal} />;
});
if (this.state.showPreviewModal) {
return (<div>
<Modal />
{childComponents}
</div>);
} else {
return (<div>
{childComponents}
</div>);
}
}
});
var ChildComponent = React.createClass({
render: function() {
return <button
className="btn btn-default btn-blue previewAsset"
onClick={this.props.togglePreviewModal} />;
}
});
Note that I'm not invoking the function in the onClick of the <button>, just passing in the prop (which is a function).

Props not setting state

I have a react component that gets a prop from another parent component. I checked in react developer tools, and the prop is for sure getting passed.
Here is my code:
var Post = React.createClass({
getInitialState: function () {
return { content: this.props.content };
},
rawMarkup: function() {
var rawMarkup = marked(this.state.content, {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
return (
<div>
{this.props.content }
<div dangerouslySetInnerHTML={ this.rawMarkup() } />
</div>
);
}
});
This results in the error: Uncaught TypeError: Cannot read property 'replace' of undefined for marked.js. However, when I setInitialState to return { content: "Blah" }; it works fine. So it looks like the prop is not set there?
But when I do the {this.props.content} in the render, it works fine?
It's just that your state is out of date. Try adding this:
getInitialState: function () {
return { content: this.props.content || '' };
},
componentWillReceiveProps: function(nextProps) {
if (this.props.content !== nextProps.content) {
this.setState({
content: nextProps.content || '',
});
}
},
Read more about components' lifecycle here.
Edit: This will solve your problem, but generally using state this way is an anti-pattern (unless content is an input or something, you haven't mentioned that in your question). What you should do instead is create a new component that will only accept content prop and render marked output. I suggest you use a stateless functional component here.
var MarkedContent = (props) => {
return <div dangerouslySetInnerHTML={{__html: marked(props.content || '', {sanitize: true})}}></div>
}
Drop this component inside your Post component like this:
var Post = React.createClass({
render: function() {
return (
<div>
<MarkedContent content={this.props.content} />
</div>
);
}
});
Thanks David Walsh!
You don't have to synchronize props with state, even more using props in state is anti-pattern. render() is called each time when props or state changed
However, it's not an anti-pattern if you make it clear that
synchronization's not the goal here
var Post = React.createClass({
rawMarkup: function() {
var rawMarkup = marked(this.props.content, {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
return (
<div>
{this.props.content }
<div dangerouslySetInnerHTML={ this.rawMarkup() } />
</div>
);
}
});
Do all your Post's have content?
I guess you are getting the list of posts from somewhere (a database) and for some of them the content is undefined, hence the:
Uncaught TypeError: Cannot read property 'replace' of undefined
Probably this.props.content has undefined as value. Then, this.state.content is initialized to undefined and when you call marked(this.state.content, {sanitize: true}) you get this error because you are passing an undefined to marked.

React form onChange->setState one step behind

I encountered this problem building a webapp and I replicated it in this jsfiddle. Essentially, I would like an input to call this.setState({message: input_val}) every time I type something into it, then pass it into the parent App class which then re-renders the message onto the Message class. However the output seems to always be one step behind what I actually type. The jsfiddle demo should be self explanatory. I am wondering if I did anything wrong to prompt this.
html
<script src="http://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id='app'></div>
js
var App = React.createClass({
getInitialState: function() {
return {message: ''}
},
appHandleSubmit: function(state) {
this.setState({message: state.message});
console.log(this.state.message);
},
render: function() {
return (
<div className='myApp'>
<MyForm onChange={this.appHandleSubmit}/>
<Message message={this.state.message}/>
</div>
);
}
});
var MyForm = React.createClass({
handleSubmit: function() {
this.props.onChange(this.state);
},
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value});
this.handleSubmit();
},
render: function() {
return (
<form className="reactForm" onChange={this.handleChange}>
<input type='text' />
</form>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div className="message">
<p>{this.props.message}</p>
</div>
)
}
});
React.render(
<App />,
document.getElementById('app')
);
A call to setState isn't synchronous. It creates a "pending state transition." (See here for more details). You should explicitly pass the new input value as part of the event being raised (like to handleSubmit in your example).
See this example.
handleSubmit: function(txt) {
this.props.onChange(txt);
},
handleChange: function(e) {
var value = e.target.value;
this.setState({message: value});
this.handleSubmit(value);
},
There is a much simpler way to do this, setState(updater, callback) is an async function and it takes the callback as second argument,
Simply pass the handleSubmit as a callback to setState method, this way after setState is complete only handleSubmit will get executed.
For eg.
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit);
}
Try to change the handleChange() method like above and it will work.
for syntax of using setState check this link
with setState hook
useEffect(() => {
your code...
}, [yourState]);
I was pulling my hair out for like an hour because of this so I decided to share... If your callback is still one step behind and seemingly not working, ensure you don't CALL the function with parenthesis... Just pass it in. Rookie mistake.
RIGHT:
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit);
}
VS
WRONG:
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit());
}
There's no reason for MyForm to be using state here. Also putting the onChange on the form instead of the input you're interested in is odd. Controlled components should be preferred because their behavior is more obvious, and any time App's message state changes (even if you e.g. allow Message to change it later), it'll be correct everywhere.
This also makes your code a bit shorter, and considerably simpler.
var App = React.createClass({
getInitialState: function() {
return {message: ''}
},
appHandleSubmit: function(message) {
this.setState({message: message});
},
render: function() {
return (
<div className='myApp'>
<MyForm onChange={this.appHandleSubmit}
message={this.state.message} />
<Message message={this.state.message}/>
</div>
);
}
});
var MyForm = React.createClass({
handleInputChange: function(e){
this.props.onChange(e.target.value);
},
// now always in sync with the parent's state
render: function() {
return (
<form className="reactForm">
<input type='text' onChange={this.handleInputChange}
value={this.props.message} />
</form>
);
}
});
jsbin
Knowing the problem is with not having asyncronous behaviour of setState I solved my issue with async await
onChangeEmail=async x =>{
await this.setState({email:x})
}
You could refactor your class-based component to a functional component as someone else mentioned. The drawbacks are that this can be quite time-consuming depending on how many code lines you have to refactor and is likely prone to error.
I will use your example of a changeHandler to display how it could be done in a functional component.
const INITIAL_DATA = {
message: ""
}
const [form, setForm] = useState({...INITIAL_DATA})
const changeHandler = (e) = setForm({
...form,
[e.target.name]: e.target.value
})
<InputField name={message} value={form.message} onChange={changeHandler}>
^ The above code will produce that behavior as you explained of onChange being one step behind the setState. As others have said, this is due to the asynchronous nature of the setState hook.
The way to solve this is to use the useEffect hook, which allows you to introduce state dependencies. So when setState is finished going through its update cycle the useEffect will fire. So add the below to the above code and BAM.. it should show the state changes without a step delay.
useEffect(() => {
doSomeValidationHere()
orSendOffTheForm()
}, [form])
*Notice how we add a dependency array after the useEffect(() => {}) hook.
Extra info:
If the dependency array is empty it will only run on component mount and first render
If there is no array, it will run every time the page renders
If there is a state name in the array it will run every time that state is finished setting
I found it very cumbersome for me to define 3 handler functions just to get some value to a component's state, so I decided not to use state at all. I just defined an additional property to the component that stored desired value.
So I ended up with a code that looked something like this:
//...
},
text: '',
handleChange: function(event) {
this.text = event.target.value;
this.forceUpdate();
},
render: function() {
return <div>
<InputComponent onChange={this.handleChange}/>
<DisplayComponent textToDisplay={this.text}/>
</div>
}
//...
or as in my case - just use onKeyUp, instead of down...
There are some solutions mentions above, some of them have some problems, but
ajsaule is the correct ansower. Here is show my Code in TypeScript Example:
// solve the problem: setstate always one step behind
useEffect(() => { isFormValid(fields, setFieldsError) }, [fields])
const handleChange = (field: string ) => (evt: React.ChangeEvent<HTMLInputElement>) => {
setFields({ ...fields, [field]: evt.target.value })
}
const isFormValid = (fields: FieldsState, setFieldsError: React.Dispatch<React.SetStateAction<TempObj>>) => {
const tempObj: TempObj = {}
const { email, password } = fields
if( !isEmail(email) ) tempObj.email = 'Invalid Email Address'
if( password.length < 8 ) tempObj.password = 'password must by atleast 8 character long'
Object.keys(fields).forEach(field => {
if(!fields[field as keyof FieldsState]) tempObj[field] = `'${field}' is emapty`
})
setFieldsError(tempObj)
return Object.values(tempObj).every( item => item == '' )
}

React waiting for map function before deciding render to prevent div flash

I am having a bit of trouble preventing a div flash where react will render the error message then receive props and finally render the props.
In my EventsView component I have the following.
view.js
var view;
if (_.size(this.props.events) !== 0) {
view = this.props.events.map(function(year) {
return <YearView year={year} />;
});
} else {
view = <NoEventsView />
}
So the variable view is rendered, initially there is no this.props.events on page load as I have another part of the file creating it. The moment this.props.events is created the events are rendered. This is done throught a react.Backbone mixin.
I have tried doing something used some of the component lifecycle methods such as componentWillUpdate. The problem is they don't seem to want to work the way I want them to work and the error message is never rendered. Example of what I have tried (among others) below
I thought of doing something like this.
view.js
componentWillUpdate: function(nextprops, nextstate) {
if (_.size(nextprops.events) !== 0) {
var view = nextprops.events.map(function(year) {
return <YearView year={year} />;
});
this.setState({
view: view
});
} else {
this.setState({
view: <NoEventsView />
})
}
},
// render this.state.view
I would then set the getInitialState:view to the a this.props.events map.
*EDIT - A temporary and terrible workaround *
So the problem I was having was I had a top level component accepting a single prop which was a global variable like so
view.js
React.render(<EventsView events={theseEvents} />, document.getElementById('mainContent'));
theseEvents is actually a Backbone Collection global variable initialized in another file.
On document load theseEvents would be unpopulated so my view would flash with the <NoEventView /> and then rerender when theseEvents is then populated. This is handled by the backbone mixin shown below.
Dan's solution pointed me in the right direction. I set a global variable window.hasEvents to true or false depending on what I got back from my ajax request but by that time React was already rendering (the component would start as hasEvents would be true, by the time it's finished hasEvents might be false). Even if I made hasEvents a prop of <EventsView /> react wouldn't rerender when it changed.
So as a temporary workaround I have defaulted hasEvents to true and rendered EventsView. I then waited for the component to load and checked whether it was in sync with hasEvents. shown below. Honestly this workaround is not very pleasant.
view.js
ar EventsView = React.createBackboneClass({
mixins: [
React.BackboneMixin("events", "all")
],
getInitialState: function() {
return {
hasEvents: window.hasEvents
}
},
componentDidMount: function() {
var model = this;
// This waits after component loading to check whether hasEvents is the same
setTimeout(function() {
if(!(model.state.hasEvents == window.hasEvents)) {
model.setState({
hasEvents: window.hasEvents
})
};
}, 1000);
},
render: function() {
var view;
if (this.props.events.length > 0) {
view = this.props.events.map(function(year) {
return <YearView year={year} />;
});
}
if (!this.state.hasEvents) {
view = <NoEventsView />
}
return {
// other parts of the page were also rendered here
{view}
}
});
essentially componentDidMount waits for a while after component mounting and then double checks a global variable. Definitely not a great workaround especially since it relies on a 1 second theseEvents population and it only works on the initial component mounting.
Your second approach is not “the React way” (although technically it may be working) because you shouldn't put React elements into state. Only data should go there, and in render if need to figure out how that data corresponds to DOM stucture. It's also best to avoid state unless absolutely necessary.
That being said, I'm not quite sure what you're trying to achieve. Assuming you want to prevent "No Events" flash before events come through, the only way your component can do that is by knowing that data is being fetched. If, in addition to events prop, you pass isLoading boolean prop, you can do that:
render: function () {
var isEmpty = this.props.events.length === 0;
if (isEmpty) {
return this.props.isLoading ?
<p>Loading...</p> : // note you can also return null here to render nothing
<NoEventsView />;
}
return this.props.events.map(function (year) {
return <YearView year={year} />;
});
}
Since I do a request right when my app starts to check if the user is Authenticated this is how I've been handling it within my components. The parent components state is linked to my store, and it passes down authenticated as a property.
I set the components hasUpdated state to false initially, since it will receive props from the parent I wait until componentWillRecieveProps is triggered and then set hasUpdated to true since it means our call has returned. Then it renders the elements it needs to.
Not sure if this is the best approach but it has worked for our needs so far.
var SomeComponent= React.createClass({
displayName: 'SomeComponent',
getInitialState: function() {
return {
hasUpdated : false
};
},
getDefaultProps: function() {
return {
authenticated : false
};
},
componentWillReceiveProps: function(nextProps) {
this.setState({ hasUpdated : true });
},
render: function() {
var AuthElement;
if(this.state.hasUpdated){
if(this.props.authenticated){
AuthElement= <div>Authenticated</div>;
}else{
AuthElement= <div>Not Authenticated</div>;
}
}
return (
{AuthElement}
);
}
});

Categories

Resources