React redux setting initial state from axios call - javascript

I have a React app which uses Redux and axios. I do not want anything to render before I have some information from the server, which I am retrieving via axios.
I thought that the best way to do this would be by initializing redux state based on that axios call.
However, my function does not seem to be returning anything in time for state initialization...
function getUserData() {
if (Auth.loggedIn()) { //leaving this here bc it could be important; Auth is another class and loggedIn returns a boolean
axios.get('/route').then(res => {
console.log(res.data); //This prints the right thing (an object)
return res.data;
});
} else {
return ' '; //This works fine; state gets initialized to ' '
}
}
let userData = getUserData();
console.log(userData); //When getUserData() returns ' ', this prints ' '. However, when getUserData() returns my object, this prints undefined.
const initialState = {
userData: userData
};
I realize that this could be a problem with getUserData() being asynchronous, and console.log(userData) running before getUserData() has finished. However, I tried:
getUserData().then(function(userData) {
console.log(userData);
});
And received 'TypeError: Cannot read property 'then' of undefined'. My function is obviously not returning a promise, so doesn't that mean it's not asynchronous?
Any ideas?
Alternatively, is there a better way of doing this? I could always set initial state and then immediately change it, and make rendering wait for the change to be complete with a conditional render, but that definitely seems worse.

You have to return promise from your getUserData function and access it using .then().
function getUserData() {
return new Promise((resolve, reject) => {
if (Auth.loggedIn()) {
axios.get('/route')
.then(res => resolve(res.data))
.catch(err => reject(err));
} else {
resolve(' ');
}
});
};
getUserData().then(function(userData) {
const initialState = {
userData: userData, // ' ' or axios result
};
console.log(initialState.userData)
});

Related

Is it possible to call to APIs inside a react-router loader function

I'd like to know if it's possible to make 2 API calls inside a loader function if I am using react-router 6. My ideas was to create an object based on these 2 calls and destruct the object in the rendering component like this:
function MainComponent (){
const {data , reservation} = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
const response = await fetch ('http://localhost:8080/rooms/' + id);
const response2 = await fetch('http://localhost:8080/rooms/reservation/' + id)
const megaResponse = {
data: response, //i tried data:{respose} It ain't work
reservation: response2,
};
if (!response.ok) {
throw json({message: 'Something Wrong'}, {status: 500});
}
else {
return megaResponse;
}
}
But i have no success output.
I'd really want to make these 2 call in one place, otherwise I will have to use useEffect in a child component. Not a good Idea I think.
Thanks
I suspect you are not returning the unpacked response, i.e. JSON. I suggest surrounding the asynchronous code in a try/catch and simply try to process the requests/responses. Unpack the JSON value from the response objects. Since it doesn't appear the requests are dependent on one another I recommend loading them into an array of Promises that can be run concurrently and awaited as a whole. If during any part of the processing a Promise is rejected or an exception thrown, the catch block will return the JSON error response to the UI, otherwise, the { data, reservation } object is returned.
const loader = async ({ request, params }) => {
const { roomId } = params;
try {
const [data, reservation] = await Promise.all([
fetch("http://localhost:8080/rooms/" + roomId),
fetch("http://localhost:8080/rooms/reservaton/" + roomId)
]).then((responses) => responses.map((response) => response.json()));
return { data, reservation };
} catch {
throw json({ message: "Something Wrong" }, { status: 500 });
}
};
I found the solution, I tried it and it worked. It is as follow:
function MainComponent (){
const [data , reservation] = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
return Promise.all([
fetch ('http://localhost:8080/rooms/' + id),
fetch('http://localhost:8080/rooms/reservation/' + id)
])
.then(
([data, reservation]) =>
Promise.all([data.json(), reservation.json()]),
error => {throw json({message: 'Something Wrong'}, {status: 500});}
)
.then(([data, reservation]) => {
return [data, reservation];
});
}
Thanks

Vuex action not waiting to finish axios promise

I encounter a strange situation developing an application in Laravel + VueJS/Vuex stack.
I understand that if a promise is not returned the parent function calling it will not wait for it to resolve so things will go asynchronous. Axios returns a promise by default when calling a resourse through http.
So i have the parent function which looks like this:
fetchInvoiceSeries() {
var arr = []
let invsrs = this.$store.getters['getInvoiceSeries']
if (invsrs == null) {
return this.$store
.dispatch('get_invoice_series')
.then(() => {
invsrs = this.$store.getters['getInvoiceSeries']
if (invsrs != null) {
invsrs.forEach(function(s) {
arr.push({
value: s.id,
text: s.series + ' / ' + s.increment
})
})
this.series = arr
} else {
console.log('Error while fetching invoice series!')
}
})
.catch(e => {
console.log(e)
})
} else {
invsrs.forEach(function(s) {
arr.push({
value: s.id,
text: s.series + ' / ' + s.increment
})
})
this.series = arr
}
}
And here is the function defined in action part of the vuex module:
get_invoice_series({ commit }) {
return get('/api/series/0')
.then(response => {
if (response.data && typeof response.data !== undefined) {
let payload = response.data
commit('SET_INVOICE_SERIES', payload)
} else {
console.log('error', error)
}
})
.catch(error => {
console.log('error', error)
})
},
So as you can see i am returning the get request from axios inside the action. In the parent i am calling the action and the "then" keyword in order to do some processing after the action it's done. Also i am using arrow function because i need the context in the parent function in order to call this.$store ...
The problem is that even after checking the getter to see if the state have the invoice series and getting them using the get_invoice_series action i still don't have the invoice series in memory judging by the code i wrote. The console keeps loggin 'Error while fetching invoice series!' the first time i execute the code and the second time (after the information exists in state), the code skips fetching the invoice series (as expected).
Can you tell me what i am doing wrong ? Thank you!
Your error comes from invsrs being null the first time, and not null the second time.
This means that your function get_invoice_series({ commit }) is asynchronous, and that it returns a promise.
For more readability, maybe you should make your call independently from your return statement, using async/await expressions :
async get_invoice_series({ commit }) {
const response = await get('/api/series/0')
if (response.data === undefined) return null
const payload = response.data
commit('SET_INVOICE_SERIES', payload)
return payload
},
And then make your calls wait for this fetch to process :
async fetchInvoiceSeries() {
let arr = []
const invsrs = await this.$store.getters['getInvoiceSeries']
// ...
It's pure conjecture here, let me know if it helps or not.

Why can't I set server's answer to state?

I'm trying to get JSON from api.openweathermap.org and set it to state, but as result I get console.log
What should I do set JSON's info to state.weather?
import React, { Component } from 'react';
class GetWeather extends Component {
constructor(props) {
super(props);
this.state = {
weather: {},
temp: ''
}
};
weather = async (e) => {
e.preventDefault();
try {
let response = await fetch('http://api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=b40640de9322c8facb1fcb9830e8b1f4');
let data = await response.json();
// if I will use response.text() here, than next console.log will show me the object literal that I got from server
console.log('data: ' + data);
await this.setState({weather: data});
console.log('state ' + this)
} catch (e) {
console.log(e);
}
}
render() {
return (
<button onClick={this.weather} />
)
}
}
export default GetWeather;
React state updates are asynchronous and occur at the end of the function call (i.e. all set state calls are "collated" and processed together, part of the reconciliation), so console logging the state update immediately after doesn't work.
Try to use the setState callback
this.setState({weather: data}, () => console.log('state', this.state));
State will update and call the callback afterwards, synchronously, so you'll see the new state value. You also do not need to await it.
You cannot await setState. To execute code after your state has changed, setState actually has 2nd argument which is a callback function that is executed after the state has changed. Your code should look something like this:
console.log(data);
this.setState({weather: data}, () => {console.log(this.state)});
Here you can see another problem. Since you are concatenating a string ('data:') with an object, your object is converted to the string and you get [object Object]. To avoid this, print either only the object or print an object separately from the string like this: console.log('data:', data). Note that I used a comma here, not a plus.

async await returning a Promise and not a value

Working on a fullstack app I am making a call to the backend that retrieves info from a DB and returns it. Thing is, when I expect to get the value, I only get a Promise {<pending>}. I have verified on the backend code that I actually get a response from the DB and send it back to the frontend so I am not sure why the promise is not being resolved. Any idea/suggestions on this?
Here is the component I am trying to call the backend on and display the information. The console.log is what displays the Promise {<pending>}
getTheAsset = async id => {
try {
const response = await this.props.getAsset(id)
.then(result => {
console.log("[DisplayAsset] Promise result: ", result);
});
} catch(error) {
console.log("[DisplayAsset] error: ", error);
}
}
render() {
const asset = this.getTheAsset(this.props.match.params.id);
console.log("[DisplayAsset] asset - ", asset);
return (
<div className="container">
</div>
);
}
The following is the redux action that makes the API call.
export const getAsset = (id) => async dispatch => {
const response = await axios.get(`http://localhost:8181/api/asset/${id}`);
dispatch({
type: GET_ASSET,
payload: response.data
});
}
I have included a snapshot of the backend, showing that I am actually getting a value back from the DB.
I have also found this great answer, but still did not have much luck applying it to my situation.
Async functions always return promises; that's what they do. Async/await exists to simplify the syntax relating to promises, but it doesn't change the fact that promises are involved.
For react components, you need to have a state value which starts off indicating that it hasn't been loaded, then you kick off your async work, and when it finishes you update the state. If necessary, you can render a placeholder while still loading.
state = {
asset: null,
}
componentDidMount() {
this.getTheAsset(this.props.match.params.id)
.then(result => this.setState({ asset: result });
}
render() {
if (this.state.asset === null) {
return null; // or some other placeholder.
} else {
return (
<div className="container">
</div>
);
}
}

How do a resolve a promise to setstate in react

I am new to react and javascript and having trouble trying to retrieve a value from a promise so that it can be used for operations. I have did some research and seen a lot of promise tutorials that can return a console log or run a function. But I have yet to see one that can allow me to save to a const/var so I can use for other operations like a setstate.
I have tried a different ways to resolve a promise from an async function so I can do a setstate but they all failed, I have narrowed it down to 3 ways that I have tried which console logs the right information, but when I setstate it fails.
This is a sample of my react component
state = {
user: {}
}
getCurrentUser = async () => {
// to save the user details if they are logged in
const jwt = localStorage.getItem('token')
return jwtDecode(jwt)
}
componentDidMount() {
// method 1
// returns a promise instead of a value so setstate fails
let user = this.getCurrentUser()
console.log(user)
this.setState({user: user})
console.log(this.state)
// method 2
// trying to resolve a promise and return a value so I save to a variable and then setstate
user = this.getCurrentUser()
user = user.then((value) => {
//console log prints out exactly what I need
console.log(value)
return value
})
console.log(user)
this.setState({user: user})
console.log(this.state)
// method 3
// trying to do setstate inside the promise also fails
user = this.getCurrentUser()
user.then((value) => {
this.setState({user: value})
})
console.log(this.state)
}
Thank you for any tips anyone might have on how to resolve this, or if I am misunderstanding concepts on async or promises.
setState is async operation, Where second parameter is callback function which is executed after setState function is performend.
you can do
let user = this.getCurrentUser();
user.then(userData => {
console.log(userData)
this.setState({user: userData}, () => {
console.log(this.state)
})
})
I don't know exactly what you need, but this.setState takes second argument as a callback, so something like this will display the updated state
this.setState({user: user}, () => console.log(this.state));
Also something like this should work:
user = this.getCurrentUser()
user = user.then((value) => {
//console log prints out exactly what I need
console.log(value)
return value
}).then((user) =>
this.setState({user}, () => console.log(this.state))
);
And you should use await in your async function to wait the data.

Categories

Resources