Alright so I am building this CRUD app with a React driven U/I. I'm having an issue with my state updating. Below are the code snippets that are involved in the problem. I've tried figuring it out myself and think it's that my state is updating correctly but it gets sent to the server before the state can be updated and when my component remounts it gets the old state from the server and overwrites the new one. The server always gets a state that is n - 1 to the actual number of items. Also if you know any ways that i could improve, please let me know I feel like this is a lot of work to do just 3 tasks!
Thank you in advance.
My state
this.state = {
newItem:{},
todos:[]
}
The retrieval methods
componentDidMount(){
this.getTodos(this.props);
}
getTodos(props){
axios.get(process.env.REACT_APP_SERVER+`/${props.match.params.id}/todos`)
.then(
(res)=>{
this.setState({todos:[...res.data]});
}
)
}
My creation and update methods
handleOnChange(e){
this.setState({newItem:{title:e.target.value, complete:false}});
}
addNewItem(){
this.setState({todos:[...this.state.todos, this.state.newItem]});
this.updateTodoList(this.props);
}
updateTodoList(props){
axios.put(process.env.REACT_APP_SERVER+`/${props.match.params.id}/todo/add`, this.state.todos).then(res=>{
console.log(res.data);
});
}
Update: Oh snap! That is what I figured, you've fixed my bug!
The problem is you're calling updateTodoList before the state is already updated.
Do this:
addNewItem(){
this.setState({todos:[...this.state.todos, this.state.newItem]}, () => {
this.updateTodoList(this.props);
});
}
setState has a callback that is invoked after state is already updated so is the place you can be sure it is, and call next fn.
The state update is an asynchronous process.
The issue here is your update API call is happening before the state
update.
To fix this issue use this syntax for state update.
addNewItem () {
this.setState (
{todos:[...this.state.todos, this.state.newItem]},
() => {
this.updateTodoList(this.props);
}
);
}
Related
I know mutating state can work against PureComponent (or similar)
Is there other reason not to mutate state?
I wonder if the 3rd way is ok to do?
// The Right Way:
// copy the existing items and add a new one
addItemImmutably = () => {
this.setState({
items: [...this.state.items, this.makeItem()]
});
};
// The Wrong Way:
// mutate items and set it back
addItemMutably = () => {
this.state.items.push(this.makeItem());
this.setState({ items: this.state.items });
};
// is this ok? (mutate but set state with new copy)
addItem3rdWay = () => {
this.state.items.push(this.makeItem());
this.setState({items: [...this.state.items]});
}
Here is an example: You have fired an async method that sends a request with your current state data. In meantime you have executed a function that mutates the state. This will cause the async function to send the mutated state despite the fact it intended to send the state before mutation.
You can think state as your database.
You don't directly mutate your database, you modify your database by API.
setState is the API.
Of course, you can directly mutate your database, but other components will have a hard time retrieving those data, because those are inconsistent right now, because somebody doesn't use the API that framework provides for you.
If you really like the way mutating state, you can use Vue, Vue is designed like that.
i have problem in react and long time i can't figure out with this.
I can't understand what happened.
What a scenario:
My app using React and Redux. I keep all my state in redux.
i set some dataRefreshed state to redux state for handling re-rendering page when data updated from api.
i'm using componentWillReceiveProps lifecycle method.
in my redux state
let initialState = {
dataRefreshed: false
}
when my request starting, in redux
case START_REQUEST:
return {
...state,
dataRefreshed: false
};
case SUCCESS_REQUEST:
return {
...state,
dataRefreshed: true
};
So in my component when i make request and get from api new data:
componentWillReceiveProps(nextProps) {
if (nextProps.Reducer.dataRefreshed) {
apiCall();
}
}
so if thinking with logic:
1- when my request start and request getting ok status, my dataRefreshed setting true
2- and here nextProps.dataRefreshed and this.props.dataRefreshed not equal.
Till here everything work well. my Condition working and apiCall() runned.
but apiCall function runned 10 times
why my state toggling one time so my state turning one to to true from false. But inside condition my function calling million times.
i can't understand what is the logic here.
I'm seriously think anymore living react because of that
This is what your code actually does:
set dataRefreshed=true
trigger props change
call apiCall() (cause dataRefreshed==true)
START_REQUEST: setting dataRefreshed=false
trigger props change (does nothing cause dataRefreshed==false)
apiCall got response from server,
SUCCESS_REQUEST: setting dataRefreshed=true
goto 2 (infinite loop)
You should introduce some flag like shouldRefresh, which will be set to false once data is fetched and not automatically set back to true unless it is meant to.
Is not that it is doing it twisted. The problem is componentWillReceiveProps method, that’s why react remove it to avoid this type of things
I have been able to find limited information on this error and was hoping someone could take a deep dive into explaining exactly what causes this. I haven't changed any of the code that appears to be showing up in the call stack recently, so I was wondering if this is from a newer update?
In my case, The error/warning was casued by the react-block-ui package. Currently there is an opened issue at github of that package. The issue hasn't been solved so far.
It's a react issue. You can check if any third-party-packages are causing this. You can check this to see exactly where the error is coming from. I found these comments from there -
// We're already rendering, so we can't synchronously flush pending work.
// This is probably a nested event dispatch triggered by a lifecycle/effect,
// like `el.focus()`. Exit.
I hope this helps.
My problem was putting the debugger inside the code. As soon as I removed it, the error went away. So just in case
I spent quite some time debugging a similar issue on my project. In the end, we were calling focus inside a setState function, but this can be quite hidden by callbacks. In our case this was looking at this:
class ChildComponent extends React.Component {
func() {
this.setState(state => {
// ... Doing something
this.props.onStateChange();
// ... Returning some state
});
}
}
And then elsewhere:
onStateChange = () => {
this.element.focus();
};
render() {
return <ChildComponent onStateChange={this.onStateChange} />;
}
I solved the problem by calling the callback in componentDidUpdate:
class ChildComponent extends React.Component {
func() {
this.setState(state => {
// ... Doing something
// ... Returning some state
});
}
componentDidUpdate(prevProps, prevState) {
if (compare(prevState, this.state)) {
this.props.onStateChange();
}
}
}
Another possible solution: A colleague of mine also suggested to use requestAnimationFrame inside setState so that the call would be happening out of the render cycle.
Hope this will help some people coming here!
I'm trying to set up a React app where clicking a map marker in one component re-renders another component on the page with data from the database and changes the URL. It works, sort of, but not well.
I'm having trouble figuring out how getting the state from Redux and getting a response back from the API fit within the React life cycle.
There are two related problems:
FIRST: The commented-out line "//APIManager.get()......" doesn't work, but the hacked-together version on the line below it does.
SECOND: The line where I'm console.log()-ing the response logs infinitely and makes infinite GET requests to my database.
Here's my component below:
class Hike extends Component {
constructor() {
super()
this.state = {
currentHike: {
id: '',
name: '',
review: {},
}
}
}
componentDidUpdate() {
const params = this.props.params
const hack = "/api/hike/" + params
// APIManager.get('/api/hike/', params, (err, response) => { // doesn't work
APIManager.get(hack, null, (err, response) => { // works
if (err) {
console.error(err)
return
}
console.log(JSON.stringify(response.result)) // SECOND
this.setState({
currentHike: response.result
})
})
}
render() {
// Allow for fields to be blank
const name = (this.state.currentHike.name == null) ? null : this.state.currentHike.name
return (
<div>
<p>testing hike component</p>
<p>{this.state.currentHike.name}</p>
</div>
)
}
}
const stateToProps = (state) => {
return {
params: state.hike.selectedHike
}
}
export default connect(stateToProps)(Hike)
Also: When I click a link on the page to go to another url, I get the following error:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
Looking at your code, I think I would architect it slightly differently
Few things:
Try to move the API calls and fetch data into a Redux action. Since API fetch is asynchronous, I think it is best to use Redux Thunk
example:
function fetchHikeById(hikeId) {
return dispatch => {
// optional: dispatch an action here to change redux state to loading
dispatch(action.loadingStarted())
const hack = "/api/hike/" + hikeId
APIManager.get(hack, null, (err, response) => {
if (err) {
console.error(err);
// if you want user to know an error happened.
// you can optionally dispatch action to store
// the error in the redux state.
dispatch(action.fetchError(err));
return;
}
dispatch(action.currentHikeReceived(response.result))
});
}
}
You can map dispatch to props for fetchHikeById also, by treating fetchHikeById like any other action creator.
Since you have a path /hike/:hikeId I assume you are also updating the route. So if you want people to book mark and save and url .../hike/2 or go back to it. You can still put the the fetch in the Hike component.
The lifecycle method you put the fetchHikeById action is.
componentDidMount() {
// assume you are using react router to pass the hikeId
// from the url '/hike/:hikeId'
const hikeId = this.props.params.hikeId;
this.props.fetchHikeById(hikeId);
}
componentWillReceiveProps(nextProps) {
// so this is when the props changed.
// so if the hikeId change, you'd have to re-fetch.
if (this.props.params.hikeId !== nextProps.params.hikeId) {
this.props.fetchHikeById(nextProps.params.hikeId)
}
}
I don't see any Redux being used at all in your code. If you plan on using Redux, you should move all that API logic into an action creator and store the API responses in your Redux Store. I understand you're quickly prototyping now. :)
Your infinite loop is caused because you chose the wrong lifecycle method. If you use the componentDidUpdate and setState, it will again cause the componentDidUpdatemethod to be called and so on. You're basically updating whenever the component is updated, if that makes any sense. :D
You could always check, before sending the API call, if the new props.params you have are different than the ones you previously had (which caused the API call). You receive the old props and state as arguments to that function.
https://facebook.github.io/react/docs/react-component.html#componentdidupdate
However, if you've decided to use Redux, I would probably move that logic to an action creator, store that response in your Redux Store and simply use that data in your connect.
The FIRST problem I cannot help with, as I do not know what this APIManager's arguments should be.
The SECOND problem is a result of you doing API requests in "componentDidUpdate()". This is essentially what happens:
Some state changes in redux.
Hike receives new props (or its state changes).
Hike renders according to the new props.
Hike has now been updated and calls your "componentDidUpdate" function.
componentDidUpdate makes the API call, and when the response comes back, it triggers setState().
Inner state of Hike is changed, which triggers an update of the component(!) -> goto step 2.
When you click on a link to another page, the infinite loop is continued and after the last API call triggered by an update of Hike is resolved, you call "setState" again, which now tries to update the state of a no-longer-mounted component, hence the warning.
The docs explain this really well I find, I would give those a thorough read.
Try making the API call in componentDidMount:
componentDidMount() {
// make your API call and then call .setState
}
Do that instead of inside of componentDidUpdate.
There are many ways to architect your API calls inside of your React app. For example, take a look at this article: React AJAX Best Practices. In case the link is broken, it outlines a few ideas:
Root Component
This is the simplest approach so it's great for prototypes and small apps.
With this approach, you build a single root/parent component that issues all your AJAX requests. The root component stores the AJAX response data in it's state, and passes that state (or a portion of it) down to child components as props.
As this is outside the scope of the question, I'll leave you to to a bit of research, but some other methods for managing state and async API calls involved libraries like Redux which is one of the de-facto state managers for React right now.
By the way, your infinite calls come from the fact that when your component updates, it's making an API call and then calling setState which updates the component again, throwing you into an infinite loop.
Still figuring out the flow of Redux because it solved the problem when I moved the API request from the Hike component to the one it was listening to.
Now the Hike component is just listening and re-rendering once the database info catches up with the re-routing and re-rendering.
Hike.js
class Hike extends Component {
constructor() {
super()
this.state = {}
}
componentDidUpdate() {
console.log('dealing with ' + JSON.stringify(this.props.currentHike))
}
render() {
if (this.props.currentHike == null || undefined) { return false }
const currentHike = this.props.currentHike
return (
<div className="sidebar">
<p>{currentHike.name}</p>
</div>
)
}
}
const stateToProps = (state) => {
return {
currentHike: state.hike.currentHike,
}
}
And "this.props.currentHikeReceived()" got moved back to the action doing everything in the other component so I no longer have to worry about the Hikes component infinitely re-rendering itself.
Map.js
onMarkerClick(id) {
const hikeId = id
// Set params to be fetched
this.props.hikeSelected(hikeId)
// GET hike data from database
const hack = "/api/hike/" + hikeId
APIManager.get(hack, null, (err, response) => {
if (err) {
console.error(err)
return
}
this.props.currentHikeReceived(response.result)
})
// Change path to clicked hike
const path = `/hike/${hikeId}`
browserHistory.push(path)
}
const stateToProps = (state) => {
return {
hikes: state.hike.list,
location: state.newHike
}
}
const dispatchToProps = (dispatch) => {
return {
currentHikeReceived: (hike) => dispatch(actions.currentHikeReceived(hike)),
hikesReceived: (hikes) => dispatch(actions.hikesReceived(hikes)),
hikeSelected: (hike) => dispatch(actions.hikeSelected(hike)),
locationAdded: (location) => dispatch(actions.locationAdded(location)),
}
}
I'm running into some problems with React.js and the state not being immediately set when calling setState(). I'm not sure if there are better ways to approach this, or if it really is just a shortcoming of React. I have two state variables, one of which is based on the other. (Fiddle of original problem: http://jsfiddle.net/kb3gN/4415/ you can see in the logs that it's not set right away when you click the button)
setAlarmTime: function(time) {
this.setState({ alarmTime: time });
this.checkAlarm();
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...
When calling setAlarmTime, since this.state.alarmTime isn't updated immediately, the following call to checkAlarm sets alarmSet based on the previous value of this.state.alarmTime and is therefore incorrect.
I solved this by moving the call to checkAlarm into the callback of setState in setAlarmTime, but having to keep track of what state is actually 'correct' and try to fit everything into callbacks seems ridiculous:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
}
Is there a better way to go about this? There are a few other places in my code which I reference state I just set and now I'm unsure as to when I can actually trust the state!
Thanks
Yes, setState is asynchronous, so this.state won't be updated immediately. Here are the unit tests for batching, which might explain some of the details.
In the example above, alarmSet is data computed from the alarmTime and elapsedTime state. Generally speaking, computed data shouldn't be stored in the state of the object, instead it should be computed as-needed as part of the render method. There is a section What Shouldn’t Go in State? at the bottom of the Interactivity and Dynamic UIs docs which gives examples of things like this which shouldn't go in state, and the What Components Should Have State? section explains some of the reasons why this might be a good idea.
As Douglas stated, it's generally not a good idea to keep computed state in this.state, but instead to recompute it each time in the component's render function, since the state will have been updated by that point.
However this won't work for me, as my component actually has its own update loop which needs to check and possibly update its state at every tick. Since we cannot count on this.state to have been updated at every tick, I created a workaround that wraps React.createClass and adds it's own internal state tracking. (Requires jQuery for $.extend) (Fiddle: http://jsfiddle.net/kb3gN/4448/)
var Utils = new function() {
this.createClass = function(object) {
return React.createClass(
$.extend(
object,
{
_state: object.getInitialState ? object.getInitialState() : {},
_setState: function(newState) {
$.extend(this._state, newState);
this.setState(newState);
}
}
)
);
}
}
For any components where you need up-to-date state outside of the render function, just replace the call to React.createClass with Utils.createClass.
You'll also have to change all this.setState calls with this._setState and this.state calls with this._state.
One last consequence of doing this is that you'll lose the auto-generated displayName property in your component. This is due to the jsx transformer replacing
var anotherComponent = React.createClass({
with
var anotherComponent = React.createClass({displayName: 'anotherComponent'.
To get around this, you'll just have to manually add in the displayName property to your objects.
Hope this helps
Unsure if this was the case when question was asked, though now the second parameter of this.setState(stateChangeObject, callback) takes an optional callback function, so can do this:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...