NodeJS, ReactJS: Axios post not updating in time - javascript

I have a really strange issue with an axios post request. In a onSubmit function on a react component I have an axios post that sends an UID and retrieves the number of items with that UID in a collection. In my case is items in the basket for the user. I want to be able to then set that number of items to the component state to use it later for validation. For some reason when I call the request and I print the res.data I get the right output which is the number of items currently in the basket for that UID. When I call with setState however it first doesn't change the state. And as I add items in the basket, it gives me the value that I had previously.
axios.post('http://localhost:5000/basket/check', {uid : UID})
.then(res => this.setState({basket: res.data}))
.catch((error) => {
console.log(error);
})
console.log(this.state.basket);
if (this.state.basket < 4) {
this.props.addBasketItem(basketItem);
}
That is my code. For example I set the basket item in the state 0. When the function gets called first two times the console.log(this.state.basket); prints 0. Then 1,2,3,4 as items get added. But the number of items in the basket is always 2 more which is really strange. What am I doing wrong?

Since setting of state is async you should use callback function in after setting state in order to be sure that only after state is set, you can use it with the freshest data. Try this...
checkBasket = () => {
const { basket } = this.state;
if (basket < 4) {
this.props.addBasketItem(basketItem);
}
}
axios.post('http://localhost:5000/basket/check', {uid : UID})
.then(res => this.setState({basket: res.data}, () => this.checkBasket())
.catch((error) => {
console.log(error);
});

As I understand you want to access the state right after you set it, we can accomplish this with setState callback.
submitHandler = () => {
axios
.post("http://localhost:5000/basket/check", { uid: UID })
.then(res =>
this.setState({ basket: res.data }, () => {
console.log(this.state.basket);
if (this.state.basket < 4) {
this.props.addBasketItem(basketItem);
}
})
)
.catch(error => {
console.log(error);
});
};
A better solution to this would be using componentDidUpdate lifecycle.
submitHandler = () => {
axios
.post("http://localhost:5000/basket/check", { uid: UID })
.then(res => this.setState({ basket: res.data }))
.catch(error => {
console.log(error);
});
};
componentDidUpdate(prevProps, prevState) {
console.log(this.state.basket);
if (this.state.basket < 4) {
this.props.addBasketItem(basketItem);
}
}

Related

Update a Table with React Hooks when a Row is Added, Deleted and Modified? [Issue: Gets Before Post and Delete]

I'm using Axios to get, put, and delete values from our database and have them displayed in a table; however, I need to refresh the page to see my changes. To find answers, I've visited these posts: How to update the page after call Axios Successful ? React, refresh table after action in react, and How can I use React Hooks to refresh a table when a row is deleted or added?
Unfortunately, I am still stuck and unsure how to dynamically update table rows upon response updates.
Update: I have noticed that the getValues function runs prior to the post and delete methods, which is why it is currently showing the previous values before the methods execute.
Axios.js - Where I am using get and delete methods. They work as I've had responses printed on the console.
import axios from "axios";
const getValues = async () => {
const values = await axios
.get("https://pokeapi.co/api/v2/type/")
.then((response) => {
return response.data;
})
.catch(function (error) {
console.log(error);
});
return values;
};
const postValues = (values) => {
axios
.post("https://pokeapi.co/api/v2/type/")
.then((response) => {
console.log("Post Values: ", response.data);
return response.data;
});
};
const deleteValues = (id) => {
console.log(id);
const deleteValues = axios
.delete(`https://pokeapi.co/api/v2/type/${id}`)
.then((response) => {
console.log("Delete Values: ", response);
})
.catch(function (error) {
console.log(error);
});
return deleteValues;
};
export { getValues, postValues, deleteValues }
ValuesTable.js - Where the delete method executes
import Axios from "./Axios";
const [data, setData] = React.useState();
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, [data]);
return (
{data.map((values) => {
<TableRow/>
<TableCell>{values.values}</TableCell>
<TableCell>
<Button
onClick={() =>
Axios.deleteValues(values.id);
}
/>
})};
)
Form.js - Where the post method executes
if (values.id === 0) {
Axios.postValues(values);
} else {
Axios.putValues(values, values.id);
}
UseState setData(result.data) loads all the existing values in the database.
Method deleteValues deletes a value in an array.
Method postValues adds a value into the database.
Well, you don't what to unconditionally call setData within an useEffect hook with data as a dependency as this will cause an infinite loop (render looping) to occur.
Since the getValues utility already unpacks the response.data value there is likely no need to do it again in your UI. Also, remove the data dependency.
useEffect(() => {
Axios.getValues()
.then((result) => {
setData(result.results);
});
}, []);
For the deleteValues utility, if console.log("Delete Values: ", response); is showing the correct values than I think you need to return this value from deleteValues.
const deleteValues = (id) => {
console.log(id);
const deleteValues = axios
.delete("https://pokeapi.co/api/v2/type/${id}`)
.then((response) => {
console.log("Delete Values: ", response);
return response; // <-- new data values
})
.catch(function (error) {
console.log(error);
});
return deleteValues;
};
Then in ValuesTable you need to update your data state with the new deleted values.
{data.map((values) => {
...
<Button
onClick={() => {
Axios.deleteValues(values.id)
.then(data => setData(data));
}}
/>
...
})};
Update
Ok, since the deleteValues utility doesn't return the updated data from the backend you will need to maintain your local state manually. I suggest doing this work in a callback handler. Upon successful deletion, update the local state.
const [data, setData] = React.useState();
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, []);
const deleteHandler = id => async () => {
try {
await Axios.deleteValues(id); // no error, assume success
setData(data => data.filter((item) => item.id !== id));
} catch(err) {
// whatever you want to do with error
}
};
return (
...
{data.map((values) => {
<TableRow/>
<TableCell>{values.values}</TableCell>
<TableCell>
<Button onClick={deleteHandler(values.id)}>
Delete
</Button>
})};
...
)
Note that I've written deleteHandler to be a curried function so you don't need an anonymous callback function for the button's onClick handler. It encloses the current id in an "instance" of the callback.
Update 2
If you are making a lot of different changes to your data in the backend it may just be easier to use a "fetch" state trigger to just refetch ("get") your data after each backend update. Anytime you make a call to update data in your DB, upon success trigger the fetch/refetch via a useEffect hook.
const [data, setData] = React.useState();
const [fetchData, setFetchData] = useState(true);
const triggerDataFetch = () => setFetchData(t => !t);
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, [fetchData]);
const deleteHandler = id => async () => {
try {
await Axios.deleteValues(id); // no error, assume success
triggerDataFetch(); // <-- trigger refetch
} catch(err) {
// whatever you want to do with error
}
};
I think you wrong in here :
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data); // result has no data property
});
}, [data]);
Please try change to this
useEffect(() => {
Axios.getValues().then((result) => {
console.log("RESULT",result); // check the actually response from API
setData(result.results); // the response of data is called results
});
}, [data]);
import axios from "axios"; const getValues = async () => { const values = await axios .get("https://pokeapi.co/api/v2/type/")
.then((response) => { return response.data; }) .catch(function (error)
{ console.log(error); }); return values; };
I don't know why but what are you trying to achieve with this. You should either use async/await clause or then clause but you are using both atleast have some good practice of coding first.
Second I think you should use async await inside try catch and remove then/catch phrases to make your code more understandable, then if you store your result inside values then simply return values.data and your problem might be resolved.
Since the deleteValues function deletes a specific object from the array on the server-side, I have decided to filter the list of objects in the array in order to remove the matching id to reflect on the front end. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
This is how I approached it.
{data.map((values) => {
...
<Button
onClick={() => {
setData(data.filter((item) => item.id !== values.id)); // <- Filter
Axios.deleteValues(values.id)
.then(data => setData(data));
}}
/>
...
})};

Firebase, React: Changing a GET request from get() method to onSnapshot() method

I've written a function that gets data from my firestore, filters through it, and returns data posted only by the logged-in user. The data, in this case, are gig listings:
authListener() {
auth().onAuthStateChanged((user) => {
if (user) {
this.setState(
{
userDetails: user
},
() =>
axios
.get(
"https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings"
)
.then((res) => {
let filteredGigs = res.data.filter((gig) => {
return gig.user === this.state.userDetails.uid;
});
this.setState({
filterGigs: filteredGigs
});
})
);
} else {
this.setState({
userDetails: null,
});
console.log("no user signed in");
}
});
}
However, when I render this data, I want it to be subscribed in real time to database changes, and I also want to be able to access the document id. In order to do this, I want to change this function to use the onSnapshot() method, here's my (rough) attempt:
authListener() {
auth().onAuthStateChanged((user) => {
if (user) {
this.setState(
{
userDetails: user
},
() =>
firebase.firestore().collection('gig-listing').onSnapshot(querySnapshot=> {
let filteredGigs = querySnapshot.filter(gig => {
return gig.user === this.state.userDetails.uid;
})
})
this.setState({
filterGigs: filteredGigs
});
})
);
This code isn't working and I have a feeling there's more than one issue, but the main error I'm getting back is querySnapshot.filter is not a function. Can anyone suggest how to re-configure my code so I can use the snapshot method, instead of my axios get request?
querySnapshot is a QuerySnapshot object. As you can see from the API documentation, there is no method filter on that object. If you want something to filter, perhaps you meant to use its docs property, which is an array of QueryDocumentSnapshot objects that contain the actual document data. So, maybe something more like this is what you want:
let filteredGigs = querySnapshot.docs.filter(snapshot => {
return snapshot.data().user === this.state.userDetails.uid;
})
If you take aa look at the docs, the onSnapshot() method returns a QuerySnapshot. The QuerySnapshot has a docs property which has all the documents in the snapshot. So you can filter them like this :
firebase.firestore().collection('gig-listing').onSnapshot(querySnapshot => querySnapshot.docs.filter(d => {
const data = d.data()
// Do whatever with data
})

Why is my data coming back in a random order, how do I make it right?

I'm making a pokedex in React, using PokeAPI. The idea is to make them like Pokemon cards. Everything is working, but the data doesn't always come back in the right order (i.e. sometimes the back of say Charizards card, maps with Bulbasaurs front). Shouldn't it always map right, since the second call goes is in a promise?
class Cardcontainer extends Component {
state= {
pokemon: [],
cardBack: []
}
componentDidMount() {
fetch('https://pokeapi.co/api/v2/pokemon')
.then(res => res.json())
.then(res=> {
this.setState({
pokemon: [...res.results]
})
})
.then(res=> {
this.state.pokemon.forEach((poke)=> {
fetch(poke.url)
.then(res => res.json())
.then(res => {
this.setState({
cardBack: [...this.state.cardBack,
res]
})
})
})
})
}
Assuming that what you're asking for is to make the calls to this.setState({cardBack: ...}) in the order that you issued the fetch() calls, then you can do that by collecting all the fetch() results in a Promise.all(). That will run them all in parallel, but collect all the results in order. Then, when they are all done, you can call this.setState() on each result in the order you issued the fetch() requests:
class Cardcontainer extends Component {
state= {
pokemon: [],
cardBack: []
}
componentDidMount() {
fetch('https://pokeapi.co/api/v2/pokemon')
.then(res => res.json())
.then(res=> {
this.setState({pokemon: [...res.results]});
return Promise.all(this.state.pokemon.map(poke=> {
return fetch(poke.url).then(res => res.json())
}));
}).then(results => {
// process all the results in order
results.forEach(res => {
this.setState({
cardBack: [...this.state.cardBack, res]
})
})
});
}
If this is not what you're asking for, then please edit your question to make the request more clear.

Rendering component before fetching data has been finished

I'm trying to fetch data by creating a function. In that function I am doing trying to set state and I am calling it from the componentDidMount method, but I am having a few problems:
I am not sure if while is good practice to be used, because I am looping and changing my endpoint so I can get new data every time.
I have tried to return data from the fetching function and use setState inside componentDidMount, but I had a problem, I suspect because componentDidMount is running before fetching has completed
I have tried to use res.json() on the data using a promise, but I got an error that res.json is not a function.
state = {
title: [],
image: [],
rating: [],
};
getData = () => {
let i = 1;
while (i <= 9) {
axios.get(`http://api.tvmaze.com/shows/${i}`)
.then(response => console.log(response))
.then(response => this.setState({
title:response.data.data.name[i],
}))
.catch(error => console.log(error));
i++;
}
};
componentDidMount() {
this.getData();
console.log(this.state.title);
}
If your goal is to render your JSX after you're done fetching information, then I'd suggest creating an additional item in your state, isLoading, that you can set to true or false and render your JSX conditionally.
Based on the example you provided below, it'd look like the following:
class Shows extends React.Component {
state = {
title: [],
image: [],
rating: [],
isLoading: true
}
componentDidMount() {
this.getData()
}
getData = () => {
// I've created a URL for each request
const requestUrls = Array.from({ length: 9 })
.map((_, idx) => `http://api.tvmaze.com/shows/${idx + 1}`);
const handleResponse = (data) => {
// `data` is an array of all shows that you've requested
// extract information about each show from the payload
const shows = data.map(show => show.data)
// handle shows data however you need it
// and don't forget to set `isLoading` state to `false`
this.setState({
isLoading: false,
title: shows.map(show => show.name),
image: shows.map(show => show.url),
rating: shows.map(show => show.rating.average),
})
}
const handleError = (error) => {
// handle errors appropriately
// and don't forget to set `isLoading` to `false`
this.setState({
isLoading: false
})
}
// use `Promise.all()` to trigger all API requests
// and resolve when all requests are completed
Promise.all(
requestUrls.map(url => axios.get(url))
)
.then(handleResponse)
.catch(handleError)
}
render() {
const { isLoading, title, image, rating } = this.state
// prevent showing your `JSX` unless data has been fetched
// ideally, show a loading spinner or something that will
// tell users that things are happening;
// returning `null` won't render anything at all
if (isLoading) {
return null
}
return (
<div>...</div>
)
}
}
This way, with Promise.all, it's a lot easier to reason about all these calls that you're making.
Other than that, using componentDidMount to fetch data from an API is the right place to do it, but I'd stay away from the while loop and use Promise.all for all your requests and map to create an array of promises (requests) that can be passed to Promise.all and handled all at once.
Working example:
CodeSandbox
The way in which you are setting state will result in the last data from api to be saved in state and it will render only last call
Do it like this
getData = () => {
let i = 1;
while (i <= 9) {
axios.get(`http://api.tvmaze.com/shows/${i}`)
.then(response =>{
let prevState=this.state.title
prevState.push(response.data.data.name[i])
this.setState({
title:prevState,
})})
.catch(error => console.log(error));
i++;
}
};

How to display item name after axios request

I try to display the item name (here the item is an ingredient) after getting it by an axios request. I don't understand what I need to do to use to "return" the item name.
Axios return the name of the item without any problem so I tried to display it with return <p>{response.data.name}</p> but nothing is displayed.
I juste have this message : "Expected to return a value in arrow function"
ListIng is called (props.recipe.ing_list = ["whateverid", "whateverid"]) :
<td><ListIng list={props.recipe.ing_list} /></td>
and I try this to display the name of the item :
const ListIng = props => (
props.list.map((item) => {
axios.get('http://localhost:4000/ingredient/' + item)
.then(response => {
return <p>{response.data.name}</p>
})
.catch(function (error) {
console.log(error)
})
})
)
It's my first post so if there is anything I can improve, don't hesitate to tell me ;-)
You are returning value from .then callback function. Returned value will be passed to nest .then if any, but will not be used as return value from functional component.
As you're using async call, you should use state, to make React know, that data is ready and component should be re-rendered. You can use React Hooks to achieve this like below (not tested, use as hint)
const ListIng = props => {
const [data, setData] = useState([]); // Initial data will be empty array
props.list.map((item) => {
axios.get('http://localhost:4000/ingredient/' + item)
.then(response => {
setData(e => ([...e, response.data.name])); // On each response - populate array with new data
})
.catch(function (error) {
console.log(error)
})
})
// Display resulting array as data comes
return <div>{data.map(d => ({<p>{d}</p>}))}</div>
}
Usually api calls should be inside componentDidMount() lifecycle method.
import React,{Component} from 'react';
const ListIng extends Component {
state={name:null};
componentDidMount() {
this.props.list.map((item) => {
axios.get('http://localhost:4000/ingredient/' + item)
.then(response => {
this.setState({name:response.data.name});
})
.catch(function (error) {
console.log(error)
})
})
}
render() {
return(
<p>{this.state.name}</p>
);
}
};

Categories

Resources