React Js how can i update the State after changes? With componentDidUpdate? - reactjs

Hello i've been trying to implement componentDidUpdate. But i have a problem. But i think im doing some part correct, like i have the If statement required? im pretty sure this worked like 2 hours ago but now it's looping in the console. what am i missing?
I will start by posting the most relevant info from my component. I also have CRUD in my component. The view is updating fine after every change but with a downside of a loop from componentDidUpdate that wont stop.
i Would appreciate some help on this matter i've tried to do some research, i guess i should not call the API again? but how can i fix this issue.
i will only post the relevant code from my component.
enter code here
state = {
productList: [],
statusMsg: "",
};
// READ FROM API
getDataFromProductsApi() {
axios
.get("https://localhost:44366/api/Products/")
.then((res) => {
console.log(res.data);
this.setState({
productList: res.data,
});
})
.catch((error) => {
console.log(error);
this.setState({ statusMsg: "Error retreiving data" });
if (axios.isCancel(error)) return;
});
}
componentDidMount() {
this.getDataFromProductsApi();
}
// reupdate the state on Changes
componentDidUpdate(prevProps, prevState) {
console.warn("changes");
if (prevState.productList !== this.state.productList) {
axios.get("https://localhost:44366/api/Products/").then((res) => {
console.log(res.data);
this.setState({
productList: res.data,
});
});
}
}
//Post
axios
.post("https://localhost:44366/api/Products/", this.state)
.then((response) => {
console.log(response);
this.setState({ statusMessage: "Product Added" });
})
.catch((error) => {
console.log(error);
this.setState({ statusMessage: "Something went wrong" });
if (axios.isCancel(error)) return;
});
};
// DELETE FROM API
deleteProduct = (productId, productName) => {
if (window.confirm("Are you sure? you want to delete")) {
axios
.delete(`https://localhost:44366/api/Products/${productId}`)
.then((response) => {
console.log(response);
this.setState({
statusMsg: `Product name: ${productName} With the ID: ${productId} was removed!`,
//updating state to show the changes in view instantly
// productList: this.state.productList.filter(
// (item) => item.id !== productId
//),
});
});
}
};

Since arrays are objects in JS, two arrays are equal only is they reference the same value(not different instances of that same value).
So using === and !== to compare arrays would be wrong.
console.log([1, 2] === [1, 2]) // returns false
let obj1 = {
name: 'Disney'
};
let obj2 = {
name: 'Disney'
}
console.log(obj1 === obj2); // returns false
console.log([obj1] == [obj2]); // returns false;
So, I guess the problem is here in (prevState.productList !== this.state.productList)
You could refer to this answer for more on
How to compare arrays in JavaScript?

Related

Issue with displaying data returned from REST API using React

I am trying out some stuff using the react-chatbot-kit in the front end and getting data from a REST API. Console.log shows the data inside .then, however I am getting the error "Uncaught TypeError: Cannot read property 'map' of undefined" when trying to output the data on the console inside the calling function. I need help to display the returned data in console.log in the function handleApiList(). Thanks in advance.
PS: I am a newbie of course in React :) since I am not clear on how to handle REST API calls that are done asynchronously. Look forward to getting this resolved. Any help and tips on resolving this will be greatly appreciated
Following is the code:
// ActionProvider starter code
class ActionProvider {
constructor(createChatBotMessage, setStateFunc) {
this.createChatBotMessage = createChatBotMessage;
this.setState = setStateFunc;
this.state = {
error: null,
users: []
}
}
greet() {
const greetingMessage = this.createChatBotMessage("Hi! Greeting!")
this.updateChatbotState(greetingMessage)
}
// This is being called when the user types in 'api' in chat window
handleApiList()
{
const { error, users } = this.state;
this.getData();
if(error) {
console.log("Error: ", error.message)
}
else {
let myarray=[]
users.map(function(user)
{
myarray += `${ user.name }\n`;
return `${ user.name }`;
})
console.log(myarray)
}
}
getData()
{
console.log("in now")
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(
(result) => {
this.setState({
users: result
});
},
(error) => {
this.setState({ error });
}
)
}
handleJobList = () => {
const message = this.createChatBotMessage(
"Fantastic, I've got the following jobs available for you",
{
widget: "jobLinks",
}
);
this.updateChatbotState(message);
};
updateChatbotState(message) {
// NOTE: This function is set in the constructor, and is passed in
// from the top level Chatbot component. The setState function here
// actually manipulates the top level state of the Chatbot, so it's
// important that we make sure that we preserve the previous state.
this.setState(prevState => ({
...prevState, messages: [...prevState.messages, message]
}))
}
}
export default ActionProvider;
You are fetching in getData and it's an async function. The data is not ready. It's better to just return the data than to setting state.
simplified version of your code.
handleApiList()
{
const { error, users } = this.state;
const data = await this.getData();
//data is ready, do what u want with the data here.
}
}
const getData = async() => {
return fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
)
}
.map returns an array, if you want to push u need to use forEach.
Example
let myarray=[]
data.forEach((user) =>
{
myarray.push(user.name });
})
console.log(myarray)
Issue description:
const { error, users } = this.state; // gets state values
this.getData(); // updates state values
if(error) {
console.log("Error: ", error.message)
}
else {
let myarray=[]
users.map(function(user) // users is value before state update
I would suggest returning from getData() a promise with result of api call. After that you can execute code in handleApiList() in .then().
Proposal:
getData()
{
console.log("in now")
return fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(
(result) => {
this.setState({
users: result
});
return result;
}
)
}
I would also move error handling to .catch().
Also have a look on this. Working using async/await instead of pure Promises is easier and cleaner ;)

React Native Firebase Chat app - Add Listener for new incoming messages

I'm creating a chat feature in my application that leverages Firebase. I've read the docs about adding event listeners, but I can't seem to get the listener to update the state (I believe when the state gets updated, the app re-renders and should have the new, incoming message)
Use Case:
you're having a chat with someone and you have the messaging/chat screen up and dont want to have to refresh the page to see new messages... they should display as the message gets posted to the DB
componentDidMount() {
this.setState({ isLoading: true })
this.pullMessages()
this.listener()
this.setState({ isLoading: false })
pullMessages = () => {
let messagesArray = []
if (this.state.chatroomDetails.users.length === 2) {
firebase.firestore().collection('chatrooms').doc('private').collection(this.state.chatroomDetails.key).doc('messages').collection('messages').get().then((querySnapshot) => {
if (!querySnapshot.empty) {
querySnapshot.forEach((doc) => {
messagesArray.push(doc.data())
this.setState({ messages: messagesArray })
})
}
})
}
else {
firebase.firestore().collection('chatrooms').doc('group').collection(this.state.chatroomDetails.key).doc('messages').collection('messages').get().then((querySnapshot) => {
if (!querySnapshot.empty) {
querySnapshot.forEach((doc) => {
messagesArray.push(doc.data())
// console.log(messagesArray)
this.setState({ messages: messagesArray })
})
} else {
console.log('Nothing to Grab')
}
})
}
}
listener = () => {
let messagesArray = []
if (this.state.chatroomDetails.users.length === 2) {
firebase.firestore().collection('chatrooms').doc('private').collection(this.state.chatroomDetails.key).doc('messages').collection('messages')
.onSnapshot(function (querySnapshot) {
if (!querySnapshot.empty) {
querySnapshot.forEach(function (doc) {
console.log('the console');
console.log(doc.data());
messagesArray.push(doc.data())
})
}
})
this.setState({ messages: messagesArray })
}
}
in listener(), i can see the new message that comes through from the other device, but i can't seem to set it to my state from here... help!
The state is likely not updating because you are mutating it. When this line runs:
messagesArray.push(doc.data())
this.setState({ messages: messagesArray })
You are adding an item to the messages array that is already in the state and then setting the state to the same value which won't re-render your component.
You can correct this by setting the messages value to a new array each time you receive an update on the collection. Like this:
this.setState({ messages: [...messagesArray, doc.data()] })
EDIT:
I have also noticed that your state update is not in the snapshot listener here:
.onSnapshot((querySnapshot) => {
if (!querySnapshot.empty) {
querySnapshot.forEach((doc) => {
console.log('the console');
console.log(doc.data());
messagesArray.push(doc.data())
})
}
})
this.setState({ messages: messagesArray })
It should look this like:
.onSnapshot((querySnapshot) => {
if (!querySnapshot.empty) {
querySnapshot.forEach((doc) => {
this.setState({ messages: [...messagesArray, doc.data() })
})
}
})
or even better to prevent needlessly updating the state:
.onSnapshot((querySnapshot) => {
if (!querySnapshot.empty) {
const newMessages = querySnapshot.map((doc) => doc.data())
this.setState({ messages: [...messagesArray, ...newMessages })
}
})

React componentDidMount not setting states before page loads

Working on a MERN application, I have a componentDidMount that uses axios to retrieve from the backend some Ids and retrieve product info(prods) from the ids. However the states in my application are still empty when the page is loaded initially, instead I'll have to make a change to state before the states are set.
I believe it might have something to do with having an array mapping in the componenDidMount, I could change the backend so in node. However i would like to see if anything could be done in the frontend first.
componentDidMount() {
axios
.get("/api/featureds")
.then(response => {
this.setState({
featureIds: response.data
});
response.data.map(({ prodId, _id }) =>
axios
.get("/api/prods/" + prodId)
.then(response => {
if (response.data == null) {
} else {
this.state.featureTempList.push(response.data);
}
})
.catch(error => {
console.log(error);
})
);
this.setState({
featureProds: this.state.featureTempList
});
})
.catch(error => {
console.log(error);
});
}
Why are you trying to set state like this?
this.state.featureTempList.push(response.data)
State should be set by this.setState().
So you can try doing this:
this.setState((oldState) => ({
featureTempList: oldState.featureTempList.push(response.data)
});
Just remember to set featureTempList to state when you initialize:
state = {
featureTempList: []
}

SetState and React-Native lifecycle

I'm taking my first steps with React-Native. I can not understand why with the following code I get the value "data" = [] inside _refreshData (console.log(this.state.data);)
I have this code from Learning React Native book:
class SimpleList extends Component {
constructor(props) {
super(props);
console.log("Inside constructor");
this.state = { data: [] };
}
componentDidMount() {
console.log("Inside componentDidMount");
this._refreshData();
}
...
_refreshData = () => {
console.log("Inside_refreshData");
console.log(NYT.fetchBooks());
NYT.fetchBooks().then(books => {
this.setState({ data: this._addKeysToBooks(books) });
});
console.log("This is data: ");
console.log(this.state.data);
};
function fetchBooks(list_name = "hardcover-fiction") {
console.log("Inside fetchBooks");
let url = `${API_STEM}/${LIST_NAME}?response-format=json&api-
key=${API_KEY}`;
return fetch(url)
.then(response => response.json())
.then(responseJson => {
return responseJson.results.books;
})
.catch(error => {
console.error(error);
});
}
Debugging (with console.log) I see that "data" = [] even if I just called the setState and from the log I see that the fetch returned my values ...
This is the call log:
Can you explain why please?
Thanks in advance.
Ok, first it's promise and asynchronous, and it's not guaranteed that when you log your data also you receive the data, so when you are in componentDidMount and call console.log(this.state.data); maybe the data is not returned yet. think it took 2000 milliseconds to return the data from api. so you call
NYT.fetchBooks().then(books => {
this.setState({ data: this._addKeysToBooks(books) });
});
and then this code as I said took 2000 milliseconds, but as I said you immediately log the data so, because at this time data is not filled you see the empty array.but if you want to see the data you can log it here :
NYT.fetchBooks().then(books => {
console.log(books);
this.setState({ data: this._addKeysToBooks(books) });
});

ReactJS setState when all nested Axios calls are finished

I have a problem with updating my state from nested axios call inside forEach loop:
constructor(props) {
super(props);
this.state = {
isLoaded: false,
items: []
};
//Binding fetch function to component's this
this.fetchFiles = this.fetchFiles.bind(this);
}
componentDidMount() {
this.fetchFiles();
}
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
The idea is to get all items from Dropbox using it's API (JavaScript SDK)
and then for each item I also need to call different API endpoint to get a temporary download link and assign it as a new property. Only after all items will get their links attached I want to setState and render the component. Could somebody please help with this, I spend already multiple hours fighting with promises :S
You could use Promise.all to wait for multiple promises. Also keep in mind that setState is async and you wont see immediate changes. You need to pass a callback.
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
// wait for all nested calls to finish
return Promise.all(items.map((item, index) => {
return axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
return item
});
}));
})
.then(items => this.setState(prevState => ({
isLoaded: true,
items: items
}), () => console.log(this.state.items)))
.catch((error) => {
console.log(error);
})
}
Try making the fetchfiles() function as an asynchronous method by adding the async keyword.Now, we have to wait till the items to get their download link, so add a await keyword before that line which makes the code to wait till the axios call gets completed.
async function fetchFiles() {
axios.get('/list')
.then(async function(response){
var items = response.data.entries;
await items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
I haven't tested the code, but it should probably work.

Resources