SetState and React-Native lifecycle - reactjs

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) });
});

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 Js how can i update the State after changes? With componentDidUpdate?

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?

How to wait for the promise to resolve and update the state

This is what my code looks like:
constructor(props) {
super(props);
this.state = {
docs: []
};
}
async componentDidMount() {
await this.quizes();
console.log(this.state.docs);
}
quizes = () => {
firebase
.firestore()
.collection("quiz")
.get()
.then(result => {
const docs = result.docs.map(doc => {
return { uid: doc.id, ...doc.data() };
});
this.setState({ docs });
});
};
Currently console.log(this.state) returns empty docs when I am trying to update it with documents from firestore.
setState is asynchronous. If you are sure that your collection is not empty then you can see your state using:
this.setState({ docs }, () => console.log(this.state);
The function as second argument of setState is run only when the asynchronous task of setting the state is done, thus you are going to see the updated state.
In order to await your quizes function it also needs to be async and use the await syntax rather than promises.
For example this code should achieve the desired outcome:
constructor(props) {
super(props);
this.state = {
docs: []
};
}
async componentDidMount() {
await this.quizes();
}
quizes = async () => {
let result = await firebase.firestore().collection("quiz").get()
const docs = result.docs.map(doc => {
return { uid: doc.id, ...doc.data() }
});
return this.setState({ docs }, () => {
console.log(this.state.docs);
});
};
EDIT:
setState uses a callback. In order to guarantee the state has been set at the time of logging, use callback within the quizes function.

where should i call the setintreval in react

I am trying to call getSession() every 5sec of delay. But in initial render i would like to call this function and execute immediately.
According to my below code, in the initial render itself it is using the delay of 5sec to display the output.
How can i achieve the following:
1. Initial render should be done immediately
2. after every 5sec getSession() should be called as well.
Current Results:
It is taking 5sec delay to display in initial render.
Expected results:
Initial render should be done immediately.
componentDidMount() {
this.getSession();
}
getSession() {
var path = "Sharing.aspx/GetSessions";
setInterval(() => {
axios
.post(path, { withCredentials: true })
.then(response => {
let element = response.data.d;
this.setState({
sessions: element
});
})
.catch(error => {
this.setState({
Errors: error
});
console.error(error);
});
},5000
);
}
render() {
return (
<div>
{this.renderSessionDetails()}
</div>
);
}
Expected results:
Initial render should be done immediately.
After every 5sec getSessions() should be called.
I would do something like this:
const INTERVAL = 6000;
class Component extends React.Component {
componentDidMount() {
this.getSession();
this.intervalId = window.setInterval(() => this.getSession(), INTERVAL);
}
componentWillUnmount() {
window.clearInterval(this.intervalId);
}
getSession() {
var path = "Sharing.aspx/GetSessions";
setInterval(() => {
axios
.post(path, { withCredentials: true })
.then(response => {
let element = response.data.d;
this.setState({
sessions: element
});
})
.catch(error => {
this.setState({
Errors: error
});
console.error(error);
});
}, 5000);
}
render() {
return <div>{this.renderSessionDetails()}</div>;
}
}
ComponentDidMount will be called only once, and at that point, you call the first getSession call, and start the interval.
An important thing to bring attention to is the call to window.clearInterval when the component gets unmounted. This is to make sure that interval doesn't keep running eternally, and worst, that more than one interval run in parallel after having this component mount a couple of times.
I hope it helps.
You could go about refactoring your code to look like that, in order to avoid waiting initially for those 5 seconds. The refactor is mainly about extracting the fetching logic away from the timer implementation. Please note that inside componentDidMount() we first call this.getSession() immediately, which is fine because we eliminated the intervals from it. Then we dispatch the intervals.
class Component extends React.Component() {
intervalId = null
componentDidMount() {
this.getSession()
this.intervalId = setInterval(() => this.getSession(), 5000)
}
componentWillUnmount() {
if (this.intervalId) {
clearInterval(this.intervalId)
}
}
getSession() {
var path = 'Sharing.aspx/GetSessions'
axios
.post(path, { withCredentials: true })
.then(response => {
let element = response.data.d
this.setState({
sessions: element
})
})
.catch(error => {
this.setState({
Errors: error
})
console.error(error)
})
}
render() {
return <div>{this.renderSessionDetails()}</div>
}
}
I would also try to make sure we're not running into race conditions here. But, if you're sure your requests never take more than 5 seconds -- it should be fine. Hope it helps!

Cannot fetch api due to array react native

I bulid an api using laravel which can run in postman (http://lkcfesnotification.000webhostapp.com/api/notifications). The problem is when i fetch using an example from this (https://www.youtube.com/watch?v=IuYo009yc8w&t=430s) where there is a array in the api then i have to setstate the array which is working well but when i try using the below code it does not render due to it is not using array in the api for example the random user api have "results" :[item], and mine one is "data":[my item]
fetchData = async () => {
const response = await fetch("https://randomuser.me/api?results=500");
const json = await response.json();
this.setState({ data: json.results });
};
if i use this will work but i want to use below code due to some homework i am doing
type Props = {};
export default class IndexScreen extends Component<Props> {
...
this.state = {
data: [],
isFetching: false,
};
_load() {
let url = "http://lkcfesnotification.000webhostapp.com/api/notifications";
this.setState({isFetching: true});
fetch(url)
.then((response) => {
if(!response.ok) {
Alert.alert('Error', response.status.toString());
throw Error('Error ' + response.status);
}
return response.json()
})
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
.catch((error) => {
console.log(error)
});
}
https://imgur.com/a/he5mNXv this is my render
the result i get the code i run is blank is loading
The fetch request is working but you are not saving the right data in the right state property.
The issues is located in the following part:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
You are assigning the response to a variable members but saving another variable data, which does not exist.
In addition, the response is an object with more information than just the data, so what you are looking for is just the data property of the response.
This should work:
.then(({ data }) => {
this.setState({data});
this.setState({isFetching: false});
})
Here we destructure the response into the variable { data }, solving your issue.
Based on the snippets you don't use the fetched data to set it to your state:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
membersis the result of your fetched json. So either rename members to data or use data: members. If the code should work like your first function it's probably data: members.result. You can also combine the two setState calls to one single call:
this.setState({
data: members.result,
isFetching: false,
});

Resources