I'm working with socket.io and react. I set up the listener in componentDidMount() and somehow the setState() function does not trigger the update. When i log the data, i can see the data sent from server. Here is my code:
this.state = {
list: []
};
public componentDidMount() {
this.socket = io('https://mybackendurl.com');
this.socket.on('an-event', data => {
console.log(data);
const list = [...this.state.list];
const updatedItemIndex = list.findIndex(
d => d.id === data.id
) as number;
list[updatedItemIndex].approvedBy = data.approvedBy;
this.setState({ list });
});
}
Can someone tell me what is the problem and how can solve this?
ComponentDidMount triggers once after the component loaded. You need to use componentDidUpdate()
And you should check
https://reactjs.org/docs/state-and-lifecycle.html and https://reactjs.org/docs/react-component.html
I can't say with full confidence that this would cause react to not see the state diff, but you are technically mutating state, which may cause react to see the old mutated state as matching the new updated state and choose not to re-render.
You do const list = [...this.state.list] to create a new list array which is good, but the nested object that you update will still be the previous reference. Try creating a new nested object as well.
this.socket.on('an-event', data => {
const list = [...this.state.list.map(item => ({...item})]; // create new object
const updatedItemIndex = list.findIndex(
d => d.id === data.id
) as number;
list[updatedItemIndex].approvedBy = data.approvedBy;
this.setState({ list });
});
Related
I'm using SSE to get a stream of strings, which I need to store in a useState to display it word by word. However, after I have changed the state and the component rerenders, the old state is retained OUTSIDE the eventSource.onmessage function. As soon as it comes inside it, the state gets the default value.
const [currentResp, setCurrentResp] = useState('');
const [respHistory, setRespHistory] = useState({
count: 0,
queryName: null,
queryData: [],
});
console.log('currentResp after it has been changed', currentResp);
const toggleRequest = () => {
if (query == respHistory.queryName) {
console.log('asd');
} else {
setRespHistory({
count: 1,
queryName: query,
...respHistory,
});
}
const url = 'https://somesseurl/sse';
const sse = new EventSource(url);
function getRealtimeData(data) {
console.log('Inside Function currentResp - ', currentResp);
const currentSSEText = data.choices[0].text;
setCurrentResp(currentResp + currentSSEText);
}
sse.onmessage = (e) => getRealtimeData(JSON.parse(e.data));
sse.onerror = (error) => {
console.log(error);
sse.close();
};
return () => {
sse.close();
console.log('Closed');
};
};
I want to retain the old state even inside the function, such that I keep concatinating the string stream that is coming from the SSE.
It is happening because you are using the current state reference to update the state.
setCurrentResp(currentResp + currentSSEText);
In react, the state updates are asynchronous, so when you update any state react puts that state update inside a queue, and then react will execute the state updates one by one from the queue and the state will be updated.
So when you're trying to update the state using the currentResp as the previous state value, it may not work because currentResp may not have the latest value.
So if you want to access the previous state value inside a state update, you can do it like below
setCurrentResp((prev) => {
return prev + currentSSEText
});
I am using react states to store data that I am getting through axios from API. And I am changing some of the key values in my react states. those states are getting updated but not getting rendered those updated states.
constructor(props) {
super(props);
this.state = {
NewOrders:[],
}
this.getNewOrders();
}
getNewOrders = async () => {
let data = await api.get(`SellerOrdersAPI/${store.getState().auth.user.id}/`).then(({data})=>data);
data.map(a =>
this.get(a)
)
console.log(this.state.NewOrders)
this.setState({NewOrders:data})
this.testt()
}
get = async(a) => {
let data1 = await itemApi.get(`/${a.item}/`).then(({data})=>data);
a.item = data1.title
}
here in console.log item value got updated but I can not render this newly updated value.
If I perform some operation on this component then this value (22) got changed by new value. but on start I can not access that.
Call getNewOrders in componentDidMount instead of constructor
componentDidMount(){
this.getNewOrders();
}
I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )
I'm creating a recipe book and I trying to get the current recipe page that is opened.
{this.state.recipes.map(res => {
const recipeId = this.state.recipeId;
if (res.id == recipeId) {
this.setState({
selectedRecipe: res
});
}
})}
As you can see, there is a recipes field in state that holds all the recipes, they are fetched from json earlier.
recipeId is the id of the current page that is openend, it is saved before too and has a value of 0 in this case.
Now what I want to do is extract the recipe with id 0 from all the recipes and save it in selectedRecipe, but for some reasons I get the next error :
"Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."
How do I fix this?
You can use filter method assuming recipes is an array declared in the state.
Example :
const selectedRecipe = this.state.recipes.filter(recipe => recipe.id === this.state.recipeId);
this.setState({ selectedRecipe });
Reorganize your code in this way:
const recipeId = this.state.recipeId
const recipe = this.state.recipes.map(res => res.id === recipeId)
this.setState({ selectedRecipe: recipe })
map isn't the thing to use here.
const selectedRecipe = this.state.recipes.find(recipe => recipe.id === this.state.recipeId);
this.setState({ selectedRecipe });
If you're calling this in render (not onClick or anything like that), you're going to enter a loop, where you're forcing a re-render that sets state, causing a re-render that sets state, causing a re-render....
In this case, you'd be better off looking at componentDidUpdate. If this is being called in an onClick, you should be ok.
Instead of trying to use setState in the render method, you can set the selectedRecipe directly after you get the recipes with the help of the array method find.
Example
componentDidMount() {
getRecipes().then(recipes => {
setState(prevState => {
const { recipeId } = prevState;
const selectedRecipe = recipes.find(res => res.id === recipeId);
return { recipes, selectedRecipe };
});
});
}
As we've known, setState is async. I've read few questions about setState, on how to use the value right after setState, but those aren't what I need right now.
I'm trying to set value for array List, and then use that List to do a function to get the value for Result. If setState isn't async, then it would be like this
`
handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
`
Is there anyway to achieve this? A documentation or keyword to research would be awesome.
Many thanks
There is a callback parameter to setState which is called after the state has been updated
this.setState({
list: resultList,
result: this.doSomething(resultList)
}, () => {
//do something with the updated this.state
});
You can use async await like
async handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: await this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
The use of this.state together with this.setState is discouraged, exactly because state updates are asynchronous and may result in race conditions.
In case updated state derives from previous state, state updater function should be used. Because it's asynchronous, event object should be treated beforehand, due to how synthetic events work in React:
const { id, value } = e.target;
this.setState(state => {
const list = [...state.list];
list[id] = value;
return {
list,
result: this.doSomething(list)
};
});