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 == '' )
}
Related
I've got a conditional that displays an editor while a certain prop remains true. The thing is, the data with which that editor is rendered with should change every time I select another object with which to populate that editor.
However, because the prop responsible for the conditional rendering doesn't change, even though the data with which the editor is rendered does, it refuses to re-render on state change.
I'm not particularly good at React, so, hopefully someone can explain how I can get around this little hiccup.
Conditional render
{this.state.showEditor ? (<BlockEditor routine={this.state.editorObject} />) : null}
Method that is being called.
handleShowEditor = routine => {
this.setState({ showEditor: true });
this.setState({ editorObject: routine });
};
The editor component
export default class BlockEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
routine: this.props.routine
};
}
render() {
return (
<div>
<Editor
autofocus
holderId="editorjs-container"
onChange={data => this.handleSave(data)}
customTools={{}}
onReady={() => console.log("Start!")}
data={this.props.routine.description}
instanceRef={instance => (this.editorInstance = instance)}
/>
</div>
);
}
}
Is there a reason for setting state separately? Why not
handleShowEditor = routine => {
this.setState({
showEditor: true,
editorObject: routine
});
};
Keep in mind that setState is asynchronous and your implementation could lead to such weird behaviour.
If you are still looking for an answer i have faced the same problem working with the same [Editor.JS][1] :).
This worked for me with functional component:
// on change fires when component re-intialize
onChange={async (e) => {
const newData = await e.saver.save();
setEditorData((prevData) => {
console.log(prevData.blocks);
console.log(newData.blocks);
if (
JSON.stringify(prevData.blocks) === JSON.stringify(newData.blocks)
) {
console.log("no data changed");
return prevData;
} else {
console.log("data changed");
return newData;
}
});
}}
// setting true to re-render when currentPage data change
enableReInitialize={true}
Here we are just checking if data changes assign it to editorData component state and perform re-render else assign prevData as it is which will not cause re-render.
Hope it helps.
Edit:
i am comparing editor data blocks change which is array.
of course you need to perform comparison of blocks more deeply than what i am doing, you can use lodash for example.
[1]: https://github.com/editor-js/awesome-editorjs
As setState is asynchronous you can make another call in its callback.
Try like this
handleShowEditor = routine => {
this.setState({
showEditor: true
}, () =>{
this.setState({
editorObject: routine
)}
});
};
I have a input field that is filtering the elements on of an array.
The search results are always one keystroke behind, I assume because setState doesn't instantly update the view? What's the best way to work around that?
class App extends Component {
constructor() {
super();
this.state = {
images:[],
searchfield: '',
filteredImages:[],
suggestedKeywords:[],
inputValue: ''
}
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value});
this.setState({inputValue: event.target.value});
let filteredImages = this.state.images.filter(image => {
return image.labels.includes(this.state.searchfield.toLowerCase());
});
console.log(event.target.value);
this.setState({filteredImages});
}
}
const SearchBox = ({searchfield, searchChange, inputValue}) => {
return (
<div>
<input
type="search"
value={inputValue}
onChange={searchChange}
placeholder="Search images..."
/>
</div>
);
}
The search results are always one keystroke behind, I assume because setState doesn't instantly update the view? What's the best way to work around that?
That isn't the problem.
Your problem is that you are assuming updates to setState occur instantly.
this.setState({searchfield: event.target.value}); //You update searchfield here
return image.labels.includes(this.state.searchfield.toLowerCase());
//but this.state.searchfield doesn't reflect the update yet!
So instead, simply work off of the updated value rather than the one from the store.
return image.labels.includes(event.target.value.toLowerCase());
setState is asynchronous, so you will be filtering by the old searchfield when you do this.state.searchfield.toLowerCase(). With this in mind you could do something like this instead:
onSearchChange = (event) => {
const { value } = event.target;
const newState = {
searchField: value,
inputValue: value
};
newState.filteredImages = this.state.images.filter(image => {
return image.labels.includes(value.toLowerCase());
});
this.setState(newState);
}
I have a problem that setState being called simultaneously by various children components
Here is some oversimplificated code:
var Content = React.createClass({
updateElements: function(element) {
elements = [].concat(this.state.elements)
elements.push(element)
this.setState({ elements })
}
render: function() {
elements = ["a", "b"];
return (
<div>
<Element updateElements={this.updateElements} data={elements[0]} />
<Element updateElements={this.updateElements} data={elements[1]} />
</div>
);
}
});
var Element = React.createClass({
componentDidMount: function() {
this.props.updateElements(this.props.data)
}
render: function() {
return (
<div>
{this.props.data}
</div>
);
}
});
ReactDOM.render(
<Content />,
document.getElementById('container')
);
Can I somehow wait for the previous state to be set before updating it once again?
According to the setState documentation:
This form of setState() is also asynchronous, and multiple calls
during the same cycle may be batched together
Which means assuming you have 2 child elements that call the following parent method:
doSomething1(amount){
this.setState({
sum1: this.state.sum1 + amount
}, () => {
console.warn(this.state.sum1); // 1
})
}
Both will log 1, because subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once.
Instead you should use the updater function form:
doSomething2(amount) {
this.setState((prevState) => {
return { sum2: prevState.sum2 + amount };
}, () => {
console.warn(this.state.sum2); // 2
});
}
Live example
This can solve your problem if your issue is depending on the previous value of the state.
I am not totally sure but this should work.If you do setState like this
this.setState((prevState, props) => {
return {elements: [...prevState.elements, element]};
});
It won't be synchronous but it should persists the element without overiding it(because of simultaneous update) as it will be taking from prevState.
I need to test the fetchData() function. I have been trying to follow this (and many other) tutorials and trying to get it to work with my function for the past 3 hours but no luck so far. I'd preferably want to do it another way anyway because I don't think jQuery and React should be used together.
I essentially want to check if 1) the function is called when the search form has been submitted (button within SearchForm clicked) and 2) check if the data comes back.
Does anyone have any suggestions on how to get started please?
Thanks
Home
export default class Home extends Component {
constructor() {
super();
this.state = {
value: '',
loading: false,
dataError: false
}
this.nodes = [];
this.fetchData = this.fetchData.bind(this);
}
fetchData(e) {
e.preventDefault();
this.setState({loading: true});
axios.get(`https://api.github.com/search/repositories?q=${this.state.value}`)
.then(res => {
this.nodes = res.data.items.map((d, k) => <RepoItem {...d} key={k}/>);
this.setState({loading: false});
})
.catch(err => {
this.setState({dataError: true});
});
}
render() {
return (
<div className="home-wrapper">
<SearchForm value={this.state.value}
onSubmit={this.fetchData}
onChange={(e) => this.setState({value: e.target.value})}/>
{this.state.loading ?
<Spinner/>:
!this.state.dataError ? this.nodes :
<h1>Oops! Something went wrong, please try again!</h1>}
</div>
);
}
}
RepoItem
export const RepoItem = props => (
<div className="repo-item">
<h1>{props.full_name}</h1>
</div>);
To check if the function is called upon form submission, you can shallow-render the <SearchForm> component with the spy function passed as a onSubmit prop. Simulate the submit event and check if the spy function is called.
To check if the data comes back, mock the axios service. You can use this library to mock axios calls. Call the fetchData() and see if the this.nodes and state updated correctly.
const wrapper = shallow(<Home />);
wrapper.instance().fetchData().then(() => {
... your assertions go here ...
})
I think it's always the best practice to return a Promise object or any chainable object from a method where asynchronous action takes place.
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>
);
}
});