Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
I'm just beginning to use React for a project, and am really struggling with incorporating async/await functionality into one of my components.
I have an asynchronous function called
fetchKey
that goes and gets an access key from an API I am serving via AWS API Gateway:
const fetchKey = async authProps => {
try {
const headers = {
Authorization: authProps.idToken // using Cognito authorizer
const response = await axios.post(
"https://MY_ENDPOINT.execute-api.us-east-1.amazonaws.com/v1/",
API_GATEWAY_POST_PAYLOAD_TEMPLATE,
headers: headers
return response.data.access_token;
} catch (e) {
console.log(`Axios request failed! : ${e}`);
return e;
I am using React's Material UI theme, and waned to make use of one of its Dashboard templates. Unfortunately, the Dashboard template uses a functional stateless component:
const Dashboard = props => {
const classes = useStyles();
const token = fetchKey(props.auth);
console.log(token);
return (
... rest of the functional component's code
The result of my console.log(token)
is a Promise, which is expected, but the screenshot in my Google Chrome browser is somewhat contradictory - is it pending, or is it resolved?
Second, if I try instead token.then((data, error)=> console.log(data, error))
, I get undefined
for both variables. This seems to indicate to me that the function has not yet completed, and therefore has not resolved any values for data
or error
. Yet, if I try to place a
const Dashboard = async props => {
const classes = useStyles();
const token = await fetchKey(props.auth);
React complains mightily:
> react-dom.development.js:57 Uncaught Invariant Violation: Objects are
> not valid as a React child (found: [object Promise]). If you meant to
> render a collection of children, use an array instead.
> in Dashboard (at App.js:89)
> in Route (at App.js:86)
> in Switch (at App.js:80)
> in div (at App.js:78)
> in Router (created by BrowserRouter)
> in BrowserRouter (at App.js:77)
> in div (at App.js:76)
> in ThemeProvider (at App.js:75)
Now, I'll be the first to state I don't have enough experience to understand what is going on with this error message. If this was a traditional React class component, I'd use the this.setState
method to set some state, and then go on my merry way. However, I don't have that option in this functional component.
How do I incorporate async/await logic into my functional React component?
Edit: So I will just say I'm an idiot. The actual response object that is returned is not response.data.access_token
. It was response.data.Item.access_token
. Doh! That's why the result was being returned as undefined, even though the actual promise was resolved.
–
–
–
You will have to make sure two things
useEffect
is similar to componentDidMount
and componentDidUpdate
, so if you use setState
here then you need to restrict the code execution at some point when used as componentDidUpdate
as shown below:
function Dashboard() {
const [token, setToken] = useState('');
useEffect(() => {
// React advises to declare the async function directly inside useEffect
async function getToken() {
const headers = {
Authorization: authProps.idToken // using Cognito authorizer
const response = await axios.post(
"https://MY_ENDPOINT.execute-api.us-east-1.amazonaws.com/v1/",
API_GATEWAY_POST_PAYLOAD_TEMPLATE,
{ headers }
const data = await response.json();
setToken(data.access_token);
// You need to restrict it at some point
// This is just dummy code and should be replaced by actual
if (!token) {
getToken();
}, []);
return (
... rest of the functional component's code
–
–
–
–
With React Hooks, you can now achieve the same thing as Class component in functional component now.
import { useState, useEffect } from 'react';
const Dashboard = props => {
const classes = useStyles();
const [token, setToken] = useState(null);
useEffect(() => {
async function getToken() {
const token = await fetchKey(props.auth);
setToken(token);
getToken();
}, [])
return (
... rest of the functional component's code
// Remember to handle the first render when token is null
Also take a look at this: Using Async await in react component
–
Component might unmount or re-render with different props.auth
before fetchKey
is resolved:
const Dashboard = props => {
const classes = useStyles();
const [token, setToken] = useState();
const [error, setError] = useState();
const unmountedRef = useRef(false);
useEffect(()=>()=>(unmountedRef.current = true), []);
useEffect(() => {
const effectStale = false; // Don't forget ; on the line before self-invoking functions
(async function() {
const response = await fetchKey(props.auth);
/* Component has been unmounted. Stop to avoid
"Warning: Can't perform a React state update on an unmounted component." */
if(unmountedRef.current) return;
/* Component has re-rendered with different someId value
Stop to avoid updating state with stale response */
if(effectStale) return;
if(response instanceof Error)
setError(response)
setToken(response);
})();
return ()=>(effectStale = true);
}, [props.auth]);
if( error )
return <>Error fetching token...{error.toString()}</>
if( ! token )
return <>Fetching token...</>
return //... rest of the functional component's code
An alternative is using Suspense and ErrorBoundary:
// render Dashboard with <DashboardSuspend>
const Dashboard = props => {
const classes = useStyles();
const [token, idToken] = props.tokenRef.current || [];
// Fetch token on first render or when props.auth.idToken has changed
if(token === void 0 || idToken !== props.auth.idToken){
/* The thrown promise will be caught by <React.Suspense> which will render
it's fallback until the promise is resolved, then it will attempt
to render the Dashboard again */
throw (async()=>{
const initRef = props.tokenRef.current;
const response = await fetchKey(props.auth);
/* Stop if tokenRef has been updated by another <Dashboard> render,
example with props.auth changed causing a re-render of
<DashboardSuspend> and the first request is slower than the second */
if(initRef !== props.tokenRef.current) return;
props.tokenRef.current = [response, props.auth.idToken];
if(props.tokenRef.current instanceof Error){
/* await fetchKey() resolved to an Error, throwing it will be caught by
<ErrorBoundary> which will render it's fallback */
throw props.tokenRef.current
return //... rest of the functional component's code
const DashboardSuspend = props => {
/* The tokenRef.current will reset to void 0 each time this component is
mounted/re-mounted. To keep the value move useRef higher up in the
hierarchy and pass it down with props or useContext. An alternative
is using an external storage object such as Redux. */
const tokenRef = useRef();
const errorFallback = (error, handleRetry)=>{
const onRetry = ()=>{
// Clear tokenRef otherwise <Dashboard> will throw same error again
tokenRef.current = void 0;
handleRetry();
return <>
Error fetching token...{error.toString()}
<Button onClick={onRetry}>Retry</Button>
const suspenseFallback = <>Fetching token...</>
return <ErrorBoundary fallback={errorFallback}>
<React.Suspense fallback={suspenseFallback}>
<Dashboard {...props} tokenRef={tokenRef} />
</React.Suspense>
</ErrorBoundary>
// Original ErrorBoundary class: https://reactjs.org/docs/error-boundaries.html
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { error };
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.log(error, errorInfo);
render() {
if (this.state.error) {
// You can render any custom fallback UI
const handleRetry = () => this.setState({ error: null });
return typeof this.props.fallback === 'function' ? this.props.fallback(this.state.error, handleRetry) : this.props.fallback
return this.props.children;
This returns a promise. To get the data from it, this is one way to do it:
let token = null;
fetchKey(props.auth).then(result => {
console.log(result)
token = result;
}).catch(e => {
console.log(e)
Let me know if that works.
I recreated a similar example: https://codesandbox.io/embed/quiet-wood-bbygk
–
–
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.