Using useState hooks within an async callback - reactjs

I'm running an async operation with a callback (ipfs in this case, but it probably doesn't matter), and I'm trying to set state using hooks within that callback. But the code isn't operating...do I need to use useEffect here maybe?
await ipfs.add(buffer, (err, ipfsHash) => {
setIpfsHash(ipfsHash);
console.log("in ipfs - ipfshash", ipfsHash);
});
setIpfsHash is blocking the inner code. The console.log does not run

The way you're handling your ipfs.add function with async/await is incorrect. But yes, you'd want to use React.useEffect in this situation.
If you want to use async/await, you need to create a new promise. In your example, ifps.add(buffer, callback) looks it's taking a callback as an argument, so this won't work.
Instead, turn it into a promise:
function add(buffer) {
return new Promise((resolve, reject) => {
const ipfs = ...
ipfs.add(buffer, (error, ipfsHash) => {
if (error) {
reject(error)
return
}
resolve(ipfsHash)
})
})
}
Then, you can use async/await in your React.useEffect like in the following example:
function App() {
const [buffer, setBuffer] = React.useState(null)
const [ipfsHash, setIpfsHash] = React.useState(null)
React.useEffect(() => {
async function doWork() {
try {
const result = await add(buffer)
setIpfsHash(result)
} catch (error) {
// handle error
}
}
doWork()
}, [buffer])
return (
<div>
...
</div>
)
}
Make sure to specify a dependency array for your useEffect hook so that it only runs when it needs to:
React.useEffect(() => {
// ...
}, [buffer])

Related

React Error: Effect callbacks are synchronous to prevent race conditions. Put the async function inside" [duplicate]

I was trying the useEffect example something like below:
useEffect(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}, []);
and I get this warning in my console. But the cleanup is optional for async calls I think. I am not sure why I get this warning. Linking sandbox for examples. https://codesandbox.io/s/24rj871r0p
For React version <=17
I suggest to look at Dan Abramov (one of the React core maintainers) answer here:
I think you're making it more complicated than it needs to be.
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
return <div>{JSON.stringify(data)}</div>
}
Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like
const response = MyAPIResource.read();
and no effects. But in the meantime you can move the async stuff to a separate function and call it.
You can read more about experimental suspense here.
If you want to use functions outside with eslint.
function OutsideUsageExample({ userId }) {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data/' + userId)
response = await response.json()
dataSet(response)
}, [userId]) // if userId changes, useEffect will run again
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
For React version >=18
Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:
In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.
If not part of the framework, you can try some libs that implement it like swr.
Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.
let fullfilled = false;
let promise;
const fetchData = () => {
if (!fullfilled) {
if (!promise) {
promise = new Promise(async (resolve) => {
const res = await fetch('api/data')
const data = await res.json()
fullfilled = true
resolve(data)
});
}
throw promise
}
};
const Main = () => {
fetchData();
return <div>Loaded</div>;
};
const App = () => (
<Suspense fallback={"Loading..."}>
<Main />
</Suspense>
);
When you use an async function like
async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}
it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that nothing is returned or a function is returned.
As a workaround for the warning you can use a self invoking async function.
useEffect(() => {
(async function() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
})();
}, []);
or to make it more cleaner you could define a function and then call it
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
the second solution will make it easier to read and will help you write code to cancel previous requests if a new one is fired or save the latest request response in state
Working codesandbox
Until React provides a better way, you can create a helper, useEffectAsync.js:
import { useEffect } from 'react';
export default function useEffectAsync(effect, inputs) {
useEffect(() => {
effect();
}, inputs);
}
Now you can pass an async function:
useEffectAsync(async () => {
const items = await fetchSomeItems();
console.log(items);
}, []);
Update
If you choose this approach, note that it's bad form. I resort to this when I know it's safe, but it's always bad form and haphazard.
Suspense for Data Fetching, which is still experimental, will solve some of the cases.
In other cases, you can model the async results as events so that you can add or remove a listener based on the component life cycle.
Or you can model the async results as an Observable so that you can subscribe and unsubscribe based on the component life cycle.
You can also use IIFE format as well to keep things short
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
(async () => {
let response = await fetch('api/data')
response = await response.json()
dataSet(response);
})();
}, [])
return <div>{JSON.stringify(data)}</div>
}
void operator could be used here.
Instead of:
React.useEffect(() => {
async function fetchData() {
}
fetchData();
}, []);
or
React.useEffect(() => {
(async function fetchData() {
})()
}, []);
you could write:
React.useEffect(() => {
void async function fetchData() {
}();
}, []);
It is a little bit cleaner and prettier.
Async effects could cause memory leaks so it is important to perform cleanup on component unmount. In case of fetch this could look like this:
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
I read through this question, and feel the best way to implement useEffect is not mentioned in the answers.
Let's say you have a network call, and would like to do something once you have the response.
For the sake of simplicity, let's store the network response in a state variable.
One might want to use action/reducer to update the store with the network response.
const [data, setData] = useState(null);
/* This would be called on initial page load */
useEffect(()=>{
fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(data => {
setData(data);
})
.catch(err => {
/* perform error handling if desired */
});
}, [])
/* This would be called when store/state data is updated */
useEffect(()=>{
if (data) {
setPosts(data.children.map(it => {
/* do what you want */
}));
}
}, [data]);
Reference => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
For other readers, the error can come from the fact that there is no brackets wrapping the async function:
Considering the async function initData
async function initData() {
}
This code will lead to your error:
useEffect(() => initData(), []);
But this one, won't:
useEffect(() => { initData(); }, []);
(Notice the brackets around initData()
For fetching from an external API using React Hooks, you should call a function that fetches from the API inside of the useEffect hook.
Like this:
async function fetchData() {
const res = await fetch("https://swapi.co/api/planets/4/");
res
.json()
.then(res => setPosts(res))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);
I strongly recommend that you do not define your query inside the useEffect Hook, because it will be re-render infinite times. And since you cannot make the useEffect async, you can make the function inside of it to be async.
In the example shown above, the API call is in another separated async function so it makes sure that the call is async and that it only happens once. Also, the useEffect's dependency array (the []) is empty, which means that it will behave just like the componentDidMount from React Class Components, it will only be executed once when the component is mounted.
For the loading text, you can use React's conditional rendering to validate if your posts are null, if they are, render a loading text, else, show the posts. The else will be true when you finish fetching data from the API and the posts are not null.
{posts === null ? <p> Loading... </p>
: posts.map((post) => (
<Link key={post._id} to={`/blog/${post.slug.current}`}>
<img src={post.mainImage.asset.url} alt={post.mainImage.alt} />
<h2>{post.title}</h2>
</Link>
))}
I see you already are using conditional rendering so I recommend you dive more into it, especially for validating if an object is null or not!
I recommend you read the following articles in case you need more information about consuming an API using Hooks.
https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd
https://reactjs.org/docs/conditional-rendering.html
try
const MyFunctionnalComponent: React.FC = props => {
useEffect(() => {
// Using an IIFE
(async function anyNameFunction() {
await loadContent();
})();
}, []);
return <div></div>;
};
Other answers have been given by many examples and are clearly explained, so I will explain them from the point of view of TypeScript type definition.
The useEffect hook TypeScript signature:
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
The type of effect:
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
Now we should know why effect can't be an async function.
useEffect(async () => {
//...
}, [])
The async function will return a JS promise with an implicit undefined value. This is not the expectation of useEffect.
Please try this
useEffect(() => {
(async () => {
const products = await api.index()
setFilteredProducts(products)
setProducts(products)
})()
}, [])
To do it properly and avoid errors: "Warning: Can't perform a React state update on an unmounted..."
useEffect(() => {
let mounted = true;
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
})();
return () => {
mounted = false;
};
}, []);
OR External functions and using an object
useEffect(() => {
let status = { mounted: true };
query(status);
return () => {
status.mounted = false;
};
}, []);
const query = async (status: { mounted: boolean }) => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (status.mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
};
OR AbortController
useEffect(() => {
const abortController = new AbortController();
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`, { signal: abortController.signal });
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
setPosts(newPosts);
} catch (e) {
if(!abortController.signal.aborted){
console.error(e);
}
}
})();
return () => {
abortController.abort();
};
}, []);
I know it is late but just I had the same problem and I wanted to share that I solved it with a function like this!
useEffect(() => {
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}) ()
}, [])
With useAsyncEffect hook provided by a custom library, safely execution of async code and making requests inside effects become trivially since it makes your code auto-cancellable (this is just one thing from the feature list). Check out the Live Demo with JSON fetching
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch";
/*
Notice: the related network request will also be aborted
Checkout your network console
*/
function TestComponent(props) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
const response = yield cpFetch(props.url).timeout(props.timeout);
return yield response.json();
},
{ states: true, deps: [props.url] }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
</div>
<button className="btn btn-warning" onClick={cancel} disabled={done}>
Cancel async effect
</button>
</div>
);
}
export default TestComponent;
The same demo using axios
Just a note about HOW AWESOME the purescript language handles this problem of stale effects with Aff monad
WITHOUT PURESCRIPT
you have to use AbortController
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
or stale (from NoahZinsmeister/web3-react example)
function Balance() {
const { account, library, chainId } = useWeb3React()
const [balance, setBalance] = React.useState()
React.useEffect((): any => {
if (!!account && !!library) {
let stale = false
library
.getBalance(account)
.then((balance: any) => {
if (!stale) {
setBalance(balance)
}
})
.catch(() => {
if (!stale) {
setBalance(null)
}
})
return () => { // NOTE: will be called every time deps changes
stale = true
setBalance(undefined)
}
}
}, [account, library, chainId]) // ensures refresh if referential identity of library doesn't change across chainIds
...
WITH PURESCRIPT
check how useAff kills it's Aff in the cleanup function
the Aff is implemented as a state machine (without promises)
but what is relevant to us here is that:
the Aff encodes how to stop the Aff - You can put your AbortController here
it will STOP running Effects (not tested) and Affs (it will not run then from the second example, so it will NOT setBalance(balance)) IF the error was thrown TO the fiber OR INSIDE the fiber
Ignore the warning, and use the useEffect hook with an async function like this:
import { useEffect, useState } from "react";
function MyComponent({ objId }) {
const [data, setData] = useState();
useEffect(() => {
if (objId === null || objId === undefined) {
return;
}
async function retrieveObjectData() {
const response = await fetch(`path/to/api/objects/${objId}/`);
const jsonData = response.json();
setData(jsonData);
}
retrieveObjectData();
}, [objId]);
if (objId === null || objId === undefined) {
return (<span>Object ID needs to be set</span>);
}
if (data) {
return (<span>Object ID is {objId}, data is {data}</span>);
}
return (<span>Loading...</span>);
}
The most easy way is to use useAsyncEffect from 'use-async-effect'
You can find it on NPM.
const ProtectedRoute = ({ children }) => {
const [isAuth, setIsAuth] = useState(false);
useAsyncEffect(async () => {
try {
const data = await axios("auth");
console.log(data);
setIsAuth(true);
} catch (error) {
console.log(error);
}
}, []);
if (!isAuth)
return <Navigate to="/signin" />
return children;
}

An array of promises is returned when using axios in map function?

I am mapping through this dummyAssests array and want to create a new array of objects based on the axios response called newAssetArray. But when I console.log the newAssetArray it just contains an array of Promises...
useEffect(() => {
let newAssetArray = dummyAssets.map(async function (asset) {
return await axios
.get(currrentPrice(asset.asset_id))
.then((response) => {
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
})
.catch((error) => console.log(error.response.data.error));
});
setAssets(newAssetArray);
console.log(newAssetArray);
}, [dummyAssets]);
Console:
Yes, that is expected. Use Promise.all() to wait for all the promises to resolve. .map() itself is not promise aware so it does not "wait" for each individual iteration of its loop to resolve before going onto the next one. So, you have the result of calling N async functions which all return a promise as your .map() result. That is how the code is supposed to work. The await you are using it not accomplishing anything. If you use a for loop instead (which is promise-aware, then your loop will be sequenced.
Here's how you could use Promise.all() to get the results:
useEffect(() => {
Promise.all(dummyAssets.map(function(asset) {
return axios
.get(currrentPrice(asset.asset_id))
.then((response) => {
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
}).catch((error) => {
console.log(error.response.data.error)
throw error;
});
})).then(newAssetArray => {
console.log(newAssetArray);
setAssets(newAssetArray);
}).catch(err => {
// do something to handle the error here
});
}, [dummyAssets]);
Or, you might find this implementation a bit simpler to follow:
useEffect(async () => {
try {
const newAssetArray = await Promise.all(dummyAssets.map(async function(asset) {
const response = await axios.get(currrentPrice(asset.asset_id));
const cp = response.data.market_data.current_price.eur;
const value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
}));
console.log(newAssetArray);
setAssets(newAssetArray);
} catch (e) {
// do something to handle the error here
console.log(e);
}
}, [dummyAssets]);
Also, note that a structure such as:
async function someFunction() {
return await axios.get()
}
does nothing different than just:
async someFunction() {
return axios.get()
}
They both return a promise that resolves to whatever axios.get() resolves to. The first returns a promise because it's in an async function and ALL async functions return a promise at the point they hit the first await. The second returns a promise because you're directly returning the axios.get() promise. Either way, they both return a promise that resolves to the same thing.
So, in general return await fn() is not helping you vs just return fn() and is not recommended. Then, once you stop using await there, you don't need async for that .map() callback anymore either.

Variable doesn't initialize after async function

I am making a request like this:
const createProgramari = async () => {
let prog = [];
try {
await axios.get('http://localhost:8000/api/programariByDentistID', {
params: {
id: JSON.parse(localStorage.getItem('user'))["id"]
}
})
.then(function (res) {
console.log(res);
if(res.status === 200) {
prog = res.data;
}
})
.catch(function (error) {
console.log(error);
});
setProgramari(prog);
} catch(e) {
console.log(e);
}
}
If I try to see this in my useEffect the variable 'programari' is an empty array (the value I initialized it with)
const [programari, setProgramari] = useState([]);
useEffect( () => {
// code to run on component mount
createProgramari();
console.log(programari);
}, [])
I tried printing the response and axios gets it right.
What am I doing wrong? Is there a place I could learn how to not do the same mistake?
The salient point here is that setProgramari is async in nature. If it is called, it doesn't necessarily imply that it will be executed right away. One way you can handle this is follows.
useEffect(() => {
createProgramari();
}, []);
// consider useMemo or useCallback based on your use case if need be
useEffect(() => {
// whatever logic you want to have
console.log(programari); // will get new value always
}, [programari])
The way you wrote the function is very confusing, I'd suggest refactoring this to
const createProgramari = async () => {
try {
const prog = (await axios.get('http://localhost:8000/api/programariByDentistID', {
params: {
id: JSON.parse(localStorage.getItem('user'))["id"]
}
})).data;
setProgramari(prog);
} catch (e) {
console.log(e)
}
}
To view data you can do something like this:
useEffect(() => {
createProgramari()
.then((someReturnedDataFromAPI) => { // Promise returned
console.log(programari)
})
}, []) // Initialization
in useEffect programari is equal with [] because setProgramari() not update already existed state in current component version, if set new state for programari, this modification propagate rerendering component
console.log(programari) work with current component state version
if you want dump programari you can move console.log outsite useEffect
in this case you get in console two dump - [] for first version and [ withData ] for version what rerender because setState()
or if you want use data from axios in useEffect you can return it how promise

Unexpected behavior when fetching data with hooks

I'm trying to fetch data with hooks, and I'm getting an unexpected result.
When I try to use an async callback with useEffect, it throws a warning saying it's bad practice, even though the code works (Commented out in the attached example below)
But when I try to declare an async function within useEffect, it doesn't work (example below)
What am I missing?
https://codesandbox.io/s/mystifying-aryabhata-glqwz?fontsize=14
You should put all the effect logic inside the fetchData() and the call it separately within the useEffect:
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios("https://hn.algolia.com/api/v1/search?query=redux");
setData(result.data);
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
You can fix it like this :
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
} catch(e) {
console.log(e);
}
}
fetchData();
}, []);
https://codesandbox.io/s/cocky-water-c0w9i
The problem in your code was, in these lines :
const result = fetchData();
setData(result.data);
here fetchData is async and will return a promise not the actual result, and so result.data is undefined
the callback passed to useEffect must return either a cleanup callback or undefined, when you mark a function as async, it returns a promise implicitly
you can create a function inside the useEffect callback that you can mark as async
React.useEffect(() => {
async function fetchData() {
// write your request
}
fetchData();
})
look to this solution in the
simple solution

Call async method in Office Fabric CommandBar onClick event using react hooks?

I have a react hook style component in typescript. I'm using office uifabric as ui framework. I want to get the following pattern to work in a "best practice" manner:
I have a component with an onClick event (in my case a CommandBar)
User clicks on the action
I make an async call to a backend api (the async part is whats causing me trouble here I think, and this is unfortunately a requirement).
When the async call is complete, or fails. I want to show a MessageBar with information.
All in all using as little code as possible, react hooks seems to have the ability to produce nice and condence code together with TypeScript. But I'm new to both things so I may be out on a limb here...
Creating a dummy fetch method that is NOT async causes my example code to work as expected. But when I have the async call, something gets lost and I do not get a re-render and visibility of the MessageBar. The api call is made though!
const UserProfile = () => {
const [apiStatusCode, setApiResponseCode] = useState(0);
const [showMessageBar, setShowMessageBar] = useState(false);
const startAsync = () => {
// With await here I get a compiler
// error. Without await, the api call is made but
// the messagebar is never rendered. If the call is not async, things
// work fine.
myAsyncMethod();
};
const toggleMessageBar = (): (() => void) => (): void => setShowMessageBar(!showMessageBar);
const myAsyncMethod = async () => {
try {
const responseData = (await get('some/data'))!.status;
setApiResponseCode(responseData);
toggleMessageBar();
} catch (error) {
console.error(error);
setApiResponseCode(-1);
toggleMessageBar();
}
};
const getItems = () => {
return [
{
key: 'button1',
name: 'Do something',
cacheKey: 'button1', //
onClick: () => startAsync()
}
];
};
return (
<Stack>
<Stack.Item>
<CommandBar
items={getItems()}
/>
</Stack.Item>
{showMessageBar &&
<MessageBar messageBarType={MessageBarType.success} onDismiss={toggleMessageBar()} dismissButtonAriaLabel="Close">
Successfully retrieved info with status code: {apiStatusCode}
</MessageBar>
}
// Other ui parts go here
</Stack>
)
};
export default UserProfile;
Assigning an async method to the onClick event gives compiler error:
Type 'Promise<void>' is not assignable to type 'void'. TS2322
Not awaiting the async call causes react not re-rendering when call is complete.
A common pattern for this sort of thing it to update your state in the callback as you are doing, and then respond to the new data with useEffect:
useEffect(()=>{
setShowMessageBar(!showMessageBar)
},[apiStatusCode, showMessageBar])
const myAsyncMethod = async () => {
try {
const responseData = (await get('some/data'))!.status;
setApiResponseCode(responseData);
// toggleMessageBar(); <-- delete me
} catch (error) {
console.error(error);
setApiResponseCode(-1);
// toggleMessageBar(); <-- delete me
}
};
Found the answer eventually, the key was to use reacts useEffect mechanism. This answer led me to the solution:
Executing async code on update of state with react-hooks
Key changes in my code:
Create a state to track execution
const [doAsyncCall, setDoAsyncCall] = useState(false);
Set the state in the onClick event:
const getItems = () => {
return [
{
key: 'button1',
name: 'Do something',
cacheKey: 'button1', //
onClick: () => setDoAsyncCall(true)
}
];
};
Wrap the async functionality in a useEffect section that depends on the state:
useEffect(() => {
async function myAsyncMethod () {
try {
const responseData = (await get('some/data'))!.status;
setApiResponseCode(responseData);
setShowMessageBar(true);
} catch (error) {
console.error(error);
setApiResponseCode(-1);
setShowMessageBar(true);
}
}
if (doAsyncCall) {
myAsyncMethod();
}
}, [doAsyncCall]);

Resources