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.
Related
I have a React app built with the Minimal template and I'm trying to follow along with one of their tutorials, in order to create a Redux slice that feeds some data to a custom component. The data itself is collected from Firebase. Below is my code:
firebase.js - helper
export function getDocuments(col) {
const colRef = collection(db, col);
const q = query(colRef, where('uid', '==', auth.currentUser.uid));
getDocs(q).then((snap) => {
const data = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
return data;
});
// return [1,2,3]
}
product.js - Redux slice
export function getProducts() {
return async (dispatch) => {
dispatch(slice.actions.startLoading());
try {
const products = await getDocuments('products');
dispatch(slice.actions.getProductsSuccess(products));
} catch (error) {
dispatch(slice.actions.hasError(error));
}
};
}
ProductList.js - component
const dispatch = useDispatch();
const { products } = useSelector((state) => state.client);
useEffect(() => {
dispatch(getProducts());
}, [dispatch]);
useEffect(() => {
if (products.length) {
// setTableData(products);
}
}, [products]);
If I console log data in the helper function (firebase.js), I get the values I expect, once the promise is resolved/fulfilled. However, if I console.log clients in the product.js slice or later in the component, I get undefined.
I assume my problem is not being able to understand how async + await + useEffect work together in order to fix this. My assumption is that I am trying to access the value before the promise is resolved and therefore before the helper function returns it. I confirmed that by returning a simple array [1, 2, 3] in my helper function as a test.
I think I am missing something fundamental here (I am not very experienced with React and JS in general and still learning things on the go). Can someone help me understand what am I doing wrong?
Thank you!
With await you can await the fulfillment or rejection of a promise, but your getDocuments Function does not return a promise. Change the last line of the function to the following:
return getDocs(q).then((snap) => {
const data = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
return data;
});
Async and Await are no different in React than in plain JavaScript:
When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method
useEffect():
By using this Hook, you tell React that your component needs to do something after rendering. This function will run every time the component is re-rendered.
Im trying to return a promise is a javascript file. However, there is a weird issue. So when I console log the returned value within the function, it shows the following:
const id = getAccounts()
.then(res => res.find(acc => acc.type === ACCOUNT_TYPES.STARTER))
.then((res) => { return res.id });
console.log(id.then(res => res))
Is there anything I am missing? Have been dealing with this and research for the whole day. If anyone can help, I would highly appreciate it!
Updated section:
const initialState = {
currentAccountId: id.then((res) => { return res; }) || ''
};
The return value of calling a Promise's .then is always another Promise. By setting currentAccountId to id.then, it will always be a Promise.
You need to call this.setState from inside the Promise's resolve function:
componentDidMount() {
getAccounts()
.then(res => res.find(acc => acc.type === ACCOUNT_TYPES.STARTER))
.then((res) => { this.setState({ currentAccountId: res }); });
}
Use componentDidMount, like the React docs suggest, to initiate an async request. "If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
Original answer
id.then will always return a new Promise, and that's what you are logging. To log the actual value you can move the console.log inside the resolve function:
id.then(res => console.log(res))
i need some understanding and a solution.
I have a seperate js-file where i handle all api calls.
So i want in my HomeScreen get the Data from firestore back.
In the Api in the then call i get my data but in the Home Screen i get only a Promise back.
I try some changes out but nothing helps. I do not understand this.
How i get in my HomeScreen the data?
Is something with my code wrong?
Api.js
export const getCurrentUserProfile = () => {
if (firebase.auth().currentUser.uid) {
const userDocID = firebase.auth().currentUser.uid;
const docRef = db.collection('Users').doc(userDocID);
docRef.get().then((doc) => {
console.log(firebase.auth().currentUser.uid);
if (doc.exists) {
console.log(doc.data()); // return the data i need
return doc.data();
} else {
console.log('No document for this ID: undefined');
}
}).catch((error) => {
console.log('Error getting document', error);
});
}
}
HomeScreen.js
const user = getCurrentUserProfile();
console.log(user);
// And here i get this back:
Promise {
"_40": 0,
"_55": null,
"_65": 0,
"_72": null,
}
const user = getCurrentUserProfile().then(data => console.log(data));
// this return as well undefined
thanks a lot
You need to keep in mind that Promises are async, and basically they are only that - promises. If you need the actual data from a promise to work with, you have 2 options:
Either chain callbacks after them with .then() (doc)
Use the await keyword (doc)
Secondly, currently you don't return anything from your getCurrentUserProfile() function. You need to return the docRef.get() method, which is a promise itself, and will resolve to doc.data().
Example:
// option 1
const user = getCurrentUserProfile().then(data => console.log(data));
// option 2
const user = await getCurrentUserProfile();
console.log(user);
Keep in mind, that you can only use await inside an async function.
Now I get an another Error. Invalid Hook Call. Hooks can only be called inside of the body of a function component. But i need only once to initialise the user to useState to access the Object. How i can do that?
Because if i move the code inside the component it runs every second...
const [userObj, setUserObj] = useState({});
getCurrentUserProfile().then(user => {
setUserObj(user);
});
const HomeScreen = props => {
return (
<SafeAreaView style={globalStyles.container}>
<Text style={globalStyles.h1}>Home</Text>
</SafeAreaView>
)
};
this pice of code finds a user by id and if is user is not found it creates one but...
I´m getting the "Expected to return a value in arrow function array-callback-return" warning
findUser(id)
.then((user) => {
if (user.empty) {
return createUser()
.then((user) => setUser(user));
} else {
const updatedUser = user.docs[0];
return {
id: updatedUser.id,
...updatedUser.data(),
};
}
})
I can´t get rid of it and I´m not sure why is happening.
thanks! hope this piece of code helps you don´t want to see it all. is a mess :)
The problem is that you're not consistently returning a value from inside findUser's .then. You should return the createUser call:
if (user.empty) {
return createUser()
.then
This is useful because it means that the resulting Promise chain will resolve when createUser and setUser finish. Otherwise, the createUser / setUser Promise will be disconnected from the outside - you won't be able to determine when it finishes, or handle its errors.
Note that you don't need an anonymous callback which calls setUser - you can pass the setUser function itself to the .then:
return createUser()
.then(setUser);
You also might consider using async / await, the control flow will be clearer:
const user = await findUser(id);
if (user.empty) {
const createdUser = await createUser();
// no need to await below if setUser doesn't return a Promise
await setUser(user);
} else {
const updatedUser = user.docs[0];
return {
id: updatedUser.id,
...updatedUser.data(),
};
}
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>
);
}
}