Update a React class component when Next Js Link used - reactjs

Original Post
I have a React class component that is used on a Next/JS Dynamic Path. On initial load all is fine. But there is a fringe use case that if I am on a page where that component is already mounted and then click a Link that is the same dynamic base as the component path but with different params, the component does not unmount and getIntialProps runs but it does not call the constructor to update the new state.
Path Examples:
Dynamic Pattern
/vos/[id]/[slug]
Initial Path
/vos/203dk2-d33d-3e3e3d/thisName
New path
/vos/554-34r4f-44d4e/aNewName
Events
1. component loads with initial props/state
2. user clicks Link to same component path but updated params
3. component lifecycles run to reset state but does not and does not unmount
4. 2-5 seconds pass... no activity
5. getIntialProps finally runs with new params
6. componentDidUpdate lifecycle called to update state with new props
I have also tried to change the Link to a Router.push() but have the same results.
Question:
Is there a way to force unmount a component to allow for a new instance of it to be created.
If not above, what would be the best way to handle this fringe case in the component lifecyces? I have tried to update the state with a function in the componentDidUpdate() cycle but this gets a bit messy as it runs before the SSR is called so state management gets out of sync.
Code Example
static getInitialProps = async (ctx: NextPageContext) => {
const services = VerifiedOrganizationProfilePage.PageServices(ctx);
const { lang: language, id: voId } = ctx.query as IProfilePagesQueryParams;
// check VO page is existing
// if VO owner or Admin
let verifiedOrganization: IVerifiedOrganizationResponse | undefined;
let user: IUserResponse | undefined;
let isVoOwner: boolean = false;
let isPlatformAdmin: boolean = false;
let isVoOwnerOrPlatformAdmin: boolean = false;
try {
verifiedOrganization = await services.verifiedOrganizationService.getVerifiedOrganization(voId);
if (!verifiedOrganization) throw new Error('No verified organization with that id was found!');
const userId = await services.cognitoIdentityService.getUserIdInSession();
if (userId) {
user = await services.usersService.getUser(userId);
isPlatformAdmin = AuthUtil.hasRoles(
[ERole.PLATFORM_ADMIN],
user.platformRoles
);
isVoOwner = OrganizationUtil.isVerifiedOrganizationOwner(
verifiedOrganization.id,
user
);
isVoOwnerOrPlatformAdmin = isVoOwner || isPlatformAdmin;
}
} catch (error) {
NextUtil.redirectTo(
'/not-found',
ctx.res,
HTTP_REDIRECT.TEMPORARY,
language
);
}
// fetch publicly visible data
const { store } = ctx;
store.dispatch(fetchCampaignsRequest({
verified_organization_id: voId,
limit: isVoOwnerOrPlatformAdmin ? EPaginationLimit.FIVE_HUNDRED : EPaginationLimit.DEFAULT,
}, ctx));
store.dispatch(fetchCausesRequest({
verified_organization_id: voId,
limit: EPaginationLimit.DEFAULT
}, ctx));
store.dispatch(fetchCommentsRequest({
verified_organization_id: voId,
limit: EPaginationLimit.DEFAULT
}, ctx));
store.dispatch(fetchUpdatesRequest({
verified_organization_id: voId,
limit: EPaginationLimit.DEFAULT
}, ctx));
// wait for redux saga updating state
await new Promise<void>((resolve) => {
const unsubscribe = store.subscribe(() => {
const state = store.getState();
if (!state.campaign.isFetching && !state.cause.isFetching && !state.comment.isFetching && !state.update.isFetching) {
unsubscribe();
resolve();
}
});
});
return {
user,
voId,
isVoOwner,
isPlatformAdmin,
verifiedOrganization,
isVoOwnerOrPlatformAdmin,
tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,
pageUrl: NextUtil.getPageUrl(ctx),
};
}
...
constructor(props){
super(props);
this.state = {...this.props}
}
...
// used to check new props from VO if coming from another VO page and set the state
static async getDerivedStateFromProps(nextProps: IVerifiedOrganizationProfilePageProps, prevState: IVerifiedOrganizationProfilePageState) {
if (nextProps.voId !== prevState.voId) {
return {
voId: nextProps.voId,
urlChanged: true,
tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,
isWaitingAdminApproval: false,
isUserBothVoAndIpRepresentative: false,
visibleBeneficiaryList: listResponse,
beneficiaryGroups: listResponse,
followingVerifiedOrganizations: {},
beneficiaryBlockchainCSVData: undefined,
userRating: undefined,
isLoading: true,
};
}
}
...
async componentDidMount() {
Router.events.on('routeChangeStart', this.handleRouteChangeComplete); // to trigger callback beofre NEXT Router/Link executes
await this.fetchPersonalData(); // method to fetch user specific data
}
...
async componentDidUpdate() {
if (this.state.urlChanged) {
await this.fetchPersonalData();
}
}
...
componentWillUnmount() {
Router.events.off('routeChangeStart', this.handleRouteChangeComplete);
}
...
// sets the current open tab to CAMPAIGNS if a VO navigates to a connected VO profile from a restricted tab
public handleRouteChangeComplete = async (url: string) => {
this.setState({tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,});
}
...
public fetchPersonalData = async () => {
const { voId, user, verifiedOrganization, isPlatformAdmin, isVoOwnerOrPlatformAdmin } = this.props;
let isVoRepresentative: boolean = false;
let isIpRepresentative: boolean = false;
let isUserBothVoAndIpRepresentative: boolean = false;
let isWaitingAdminApproval: boolean = false;
let visibleBeneficiaryList: IListResponse<IBeneficiaryWithInvitationStatus> | undefined;
let beneficiaryGroups: IListResponse<IGroup> | undefined;
try {
const services = VerifiedOrganizationProfilePage.PageServices();
if (user) {
isWaitingAdminApproval = verifiedOrganization.verifiedOrganizationStatus === EVerifiedOrganizationStatus.PENDING_PLATFORM_ADMIN_APPROVAL;
// If Verified Organization is waiting for Admin Platform approval, only Platform Admin can see the page.
if (isWaitingAdminApproval && !isPlatformAdmin) {
throw new NotFoundError();
}
isVoRepresentative = AuthUtil.hasRoles(
[ERole.VERIFIED_ORGANIZATION_REPRESENTATIVE],
user.platformRoles
);
isIpRepresentative = AuthUtil.hasRoles(
[ERole.IMPLEMENTING_PARTNER_REPRESENTATIVE],
user.platformRoles
);
isUserBothVoAndIpRepresentative =
isVoRepresentative && isIpRepresentative;
// If Verified Organization is waiting for Admin Platform approval, only Platform Admin can see the page.
if (isWaitingAdminApproval && !isPlatformAdmin) {
throw new NotFoundError();
}
// add the prefix to the id so we can match the record in the Connections table.
const prefixedId = EIdTypes.VERIFIED_ORGANIZATION.toUpperCase() + '#' + verifiedOrganization.id;
// Fetch data visible only to VoOwner and Aidonic
const connections = [] as unknown as IListResponse<IConnectionVOIP>;
if (isVoOwnerOrPlatformAdmin) {
// Get from the API all the connections sent or received
// Commenting this out as it calling twice the API. The call to the API is done from the Tab instead.
// connections = await services.connectionsService.getVisibleConnectionsByOrganization(prefixedId);
visibleBeneficiaryList = await services.beneficiaryService.getBeneficiariesVisibleToOrganization(prefixedId);
beneficiaryGroups = await services.beneficiaryGroupsService.getBeneficiaryGroupsList(prefixedId, {limit: EPaginationLimit.THIRTY});
}
const follows = await services.followsService.getFollowsList({
user_id: user.id
});
const [followingVerifiedOrganizations] = mapFollowsByKey(follows, [
'verifiedOrganizationId'
]);
const userRating = await services.ratingsService.getRatingList({
user_id: user.id,
verified_organization_id: verifiedOrganization.id
});
this.setState({
voId,
connections,
tabValue: EVerifiedOrganizationProfilePageTabs.CAMPAIGNS,
beneficiaryGroups,
isWaitingAdminApproval,
visibleBeneficiaryList,
followingVerifiedOrganizations,
isUserBothVoAndIpRepresentative,
userRating: userRating && userRating[0],
isLoading: false,
urlChanged: false
});
}
} catch (e) {
console.log('Error in data fetching on VO profile page: ', e);
}
}
Update
I have split the props from the state to maintain one source of true and used getDerivedStateFromProps() to catch the change and call fetchPersonalData(). All working well.
The only issue is it seems to take twice as long to load the new updated props/state than initial load. Thoughts?

Solution:
For my case this was cause by a API call in the lifecycle. Framework worked fine.

Related

React component uses old data from previous API call

I am using React Query to fetch data from an API I have built. The component is rendering the old data from the previous api call and not updating with new the data from the new api call.
The new data is only rendering when I refresh the page.
Component:
export const ProfilePageStats = (props: {
user: User;
id: number;
}) => {
const { chatId } = useParams();
const { status: subscribeStatus, data: subscribeData } =
useSubscriptionsWithType(
chatId ? chatId : "",
props.id,
props.user.id,
"SUBSCRIBE"
);
const { status: unsubscribeStatus, data: unsubscribeData } =
useSubscriptionsWithType(
chatId ? chatId : "",
props.id,
props.user.id,
"UNSUBSCRIBE"
);
if (unsubscribeStatus == "success" && subscribeStatus == "success") {
console.log("Working", unsubscribeData);
return (
<ProfilePageStatsWithData
user={props.user}
subscribed={Object.keys(subscribeData).length}
unsubscribed={Object.keys(unsubscribeData).length}
/>
);
}
if (unsubscribeStatus == "error" && subscribeStatus == "error") {
console.log("error");
return <ProfilePageStatsLoading />;
}
if (unsubscribeStatus == "loading" && subscribeStatus == "loading") {
console.log("loading");
return <ProfilePageStatsLoading />;
}
return <ProfilePageStatsLoading />;
};
export const useSubscriptionsWithType = (
chatId: string,
id: number,
userId: number,
type: string
) => {
return useQuery(
["subscriptionsWithType"],
async () => {
const { data } = await api.get(
`${chatId}/subscriptions/${id}/${userId}?type=${type}`
);
return data;
},
{
enabled: chatId > 0 && userId > 0,
refetchOnWindowFocus: false,
}
);
};
The component should update to show the new user values but shows the previous user values. If I click out and select a different user entirely it then shows the values for the previously clicked user.
I can see that React Query is fetching with the correct values for the query but the component still renders the old user data?
It turns out that the fetchStatus value is changing to "fetching" but it not actually calling the api. Hence, why its only using the old values?
Your key part of the useQuery is what tells the hook when to update.
You only use ["subscriptionsWithType"] as key, so it will never know that you need to refetch something.
If you add userId there, it will update when that changes.
So, using
return useQuery(
["subscriptionsWithType", userId],
async () => {
...
will work.
It is likely, that you want all the params, that you use in the url, to be added there.
I solved it by adding a useEffect and refetching based on the changing user id.
useEffect(() => {
refetch();
}, [props.user.id]);

How can I extract billing details from card element during button onClick?

We have the following function that runs when our stripe Start Subscription button is clicked:
const handleSubmit = async (ev) => {
setIsProcessing(true);
const cardElement = elements.getElement('card');
try {
// Grab Stripe Elements + Variables needed to post payment subscription
cardElement.update({ disabled: true });
const apiBaseUrl = config.url.API_URL;
const id = userData.user ? userData.user._id : null;
// Grab Billing (zip only) & Create Payment Method
const billingDetails = {
address: {
postal_code: ev.target.zip.value
}
};
const paymentMethodReq = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: billingDetails
});
// Handle Creating Payment Subscription
if (paymentMethodReq.error) { ... return } // handle bad payment
const paymentObject = { id, paymentMethod: paymentMethodReq.paymentMethod.id }; // address
const response = await Axios.post(`${apiBaseUrl}/stripe/payment_subscriptions`, paymentObject);
setIsProcessing(false);
} catch (error) {
... handle error
}
};
and our subscribe button that calls the handleSubmit function
<button
className='stripe-button'
onClick={handleSubmit}
disabled={isProcessing || !stripe}
>
Start Subscription
</button>
From our understanding of the stripe docs - https://stripe.com/docs/api/payment_methods/object?lang=node - we need to pass billing_details into the createPaymentMethod function. However, handleSubmit throws an error because ev.target.zip is undefined. (I believe) since we are submitting via a button onClick (rather than a form onSubmit), that the ev.target does not have what we want. Here is the console.log for ev, ev.target and for our cardElement:
How can the billing details be extracted from the cardElement, from within the handleSubmit function, so that they can be passed to the createPaymentMethod function?

store data in firestore when Browser Tab is closed or the route is changed (react JS)

const handleDraftContracts = async () => {
console.log('/bruhhhhhhandleDraftContract');
const paragraphRef: string | any = document.getElementById('contract');
const contractDetails = {
contractName: 'House Rental',
states: {
amount: amount,
},
content: paragraphRef?.textContent,
};
await makeDraftContract(contractDetails);
};
useEffect(() => {
console.log('///////I am hreeeee');
window.addEventListener('onbeforeunload', (env) => {
handleDraftContracts();
});
return () => {
console.log('///////removing');
window.removeEventListener('onbeforeunload', handleDraftContracts);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
firestore.js
// make Draft Contracts
export async function makeDraftContract(contractDetails: object | any) {
try {
console.log("making a draft contract", contractDetails);
const draftContractRef: any = collection(db,"makeDraftContracts");
let contract = await addDoc(draftContractRef, contractDetails);
console.log("./////////makeDraftContract", contract);
} catch (error) {
console.log('////errror in contract Hanlder', error);
}
}
I want to call my handleDraftContracts method whenever user closes the tab or changes the route. I am using onbeforeunload event. The handleDraftContracts is getting called but the tab unloads before Firestore could update the collection. How can I get around this that as the user closes the tab or move to a new route, my firestore method get executed first then the tab gets unloaded ?
Try with Beacon api
https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API
as 'onbeforeunload' cannot make sure you request to server has been made and requests can slow down the browser
componentWillUnmount is like that one, cannot to make long running script.

Rendering elements of array fetched with React does not work

I'm successfully fetching an array of objects from a local ethereum blockchain (successful as in, I've logged the data in componentDidMount and it is what its supposed to be). Its a dynamically sized array
(inventory), so I gotta fetch it element by element. Here are the relevant parts:
async componentDidMount() {
this.setState({fetching: true}, () => {
this.loadData()
.then(result => this.setState({fetching: false}))
})
}
async loadData() {
if (typeof window.ethereum !== 'undefined') {
const web3 = new Web3(window.ethereum)
const netId = await web3.eth.net.getId()
const accounts = await web3.eth.getAccounts()
this.setState({account: accounts[0]})
if(typeof accounts[0] !== 'undefined'){
const balance = await web3.eth.getBalance(accounts[0])
this.setState({balance: balance})
}
else {
window.alert('Please login with MetaMask')
}
try {
const plotRepository = new web3.eth.Contract(PlotRepository.abi, PlotRepository.networks[netId].address)
const plot = await plotRepository.methods.claimPlot(this.state.account).send({from: this.state.account})
this.setState({plot: new web3.eth.Contract(Plot.abi, _plot.plot)})
const size = await this.state.plot.methods.size().call()
const tiles = await this.state.plot.methods.getTiles().call()
await this.loadInventory() // this is where I'm loading the array in question
this.setState({size: size})
this.setState({tiles: tiles})
// logging the array here works
} catch (e) {
window.alert(e)
}
} else {
window.alert('Please install MetaMask')
}
}
async loadInventory() {
for (const [key, value] of Object.entries(inventoryinfo)) {
const item = await this.state.plot.methods.getInventoryItem(key).call()
this.setState({inventory: [...this.state.inventory, {
name: key,
value: parseInt(item[0][1]),
count: item[1]
}]})
}
}
Again, logging the array in the fetching functions works just fine. I'm also using a flag (fetching) which denotes whether all data has been successfully loaded or not, and I only try to render data once everything is loaded:
render() {
const { plot, account, fetching, tiles, inventory } = this.state
return (
<div className="App">
<p>Currently logged in with account: { this.state.account }</p>
<p>Balance: { this.state.balance } </p>
{ fetching ? // make sure things are fetched before rendering them
<p>Loading</p> :
<div>
<UserPlot tileData={tiles} plot={plot} account={account}/>
{
inventory[0].name // inventory[0] is undefined
// inventory.length is not
}
</div>
}
</div>
);
}
Trying to display any element of the array gives me an undefined error. Rendering the array length works though ({inventory.length}). This is weird since I'm doing the same thing with another array that I'm fetching and displaying in the UserPlot component and that works just fine (only difference is that in this case the array is static and I can load it all in one go but I don't think that has anything to do with it)

React.js Updating state where multiple API endpoints are involved

I'm currently trying to get a project working to test some things and I'm stuck at a point where I'm trying to update the state properly.
I have an endpoint accessed via axios.get("/docker/containers") which will return an array for all IDs of the containers which are currently running on my system this is done like so:
componentDidMount() {
this.interval = setInterval(() => this.updateContainers(), 3000);
};
componentWillUnmount() {
clearInterval(this.interval);
}
At this point my state looks like this:
state = {
containers: [{id: 'id1'}, {id: 'id2'}]
}
The user interface then just shows a list of IDs.
I can then click on an ID on my user interface and it will set a watcher:
state = {
containers: [{id: 'id1', watcher: true}, {id: 'id2'}]
}
The point of the watcher is so that on the next update cycle more detailed information about a particular container is retrieved.
state = {
containers: [{id: 'id1', watcher: true, name: 'container1'}, {id: 'id2'}]
}
Upon clicking the container in the user interface where a watcher is already set then the watcher is dropped and the more detailed information is then no longer retrieved
state = {
containers: [{id: 'id1', watcher: false}, {id: 'id2'}]
}
Where I'm getting stuck is on how to get the more detailed information. My updateContainers method has 3 steps:
Read the response from the API and destruct the state into separate variables, compare the state var with the response var and remove any containers that have gone down (no setState is done here).
Add any new containers from the response to the state that have since come up (again no setState).
...All good thus far...
Loop through the filtered array of containers from steps 1 and 2 and find any containers where a watcher is set. Where it is set perform an API call to retrieve the more detailed info. Finally set the state.
In step 3 I use a forEach on the filtered array and then do an axios.get("/docker/containers/id1") where a watcher has been set otherwise simply keep the container details I already have but that's where I get stuck, Typescript is also giving me the error:
TS2322: Type 'void' is not assignable to type 'IndividualContainer[]'.
currently I have:
updateContainers() {
axios.get('/docker/containers')
.then(response => {
const apiRequestedContainers: string[] = response.data.containers;
// array of only IDs
const stateContainers: IndividualContainer[] = [
...this.state.containers
];
// remove dead containers from state by copying still live containers
let filteredContainers: IndividualContainer[] = [
...this.filterOutContainers(stateContainers, apiRequestedContainers)
];
// add new containers
filteredContainers = this.addContainerToArray(
filteredContainers, apiRequestedContainers
);
return this.updateContainer(filteredContainers);
})
.then(finalArray => {
const newState: CState = {'containers': finalArray};
this.setState(newState);
});
};
updateContainer(containers: IndividualContainer[]) {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get('/docker/containers/' + container.id)
.then(response => {
// read currently available array of containers into an array
const resp = response.data;
resp['id'] = container.id;
resp['watcher'] = true;
returnArray.push(resp);
});
} else {
returnArray.push(container);
}
return returnArray;
});
};
Any pointers to where my logic fails would be appreciated!
Edit:
Render Method:
render() {
const containers: any = [];
const curStateOfContainers: IndividualContainer[] = [...this.state.containers];
if (curStateOfContainers.length > 0) {
curStateOfContainers.map(container => {
const container_id = container.id.slice(0, 12);
containers.push(
<Container
key = {container_id}
container_id = {container.id}
name = {container.name}
clickHandler = {() => this.setWatcher(container.id)}
/>
);
});
}
return containers;
}
I'm not an expert in TypeScript so I had to change the response to JS and thought you'll re-write it in TS in case it's needed.
async updateContainers() {
const response = await axios.get('/docker/containers')
const apiRequestedContainers = response.data.containers; // array of only IDs
const stateContainers = [...this.state.containers];
// remove dead containers from state by copying still live containers
let filteredContainers = [...this.filterOutContainers(stateContainers, apiRequestedContainers)];
// add new containers
filteredContainers = this.addContainerToArray(filteredContainers, apiRequestedContainers);
const containers = await this.updateContainer(filteredContainers)
this.setState({ containers });
};
async updateContainer(containers) {
return containers.map(async (container) => {
if (container.watcher) {
const response = await axios.get('/docker/containers/' + container.id)
// read currently available array of containers into an array
return {
...response.data,
id: container.id,
watcher: true,
}
} else {
return container;
}
});
}
Here's what I've updated in updateContainer:
I'm now mapping the array instead of doing a forEach
I'm now waiting for the container details API to return a value before checking the second container. --> this was the main issue as your code doesn't wait for the API to finish ( await / async )
The problem is that you are returning nothing from updateContainer method which will return void implicitly:
// This function return void
updateContainer(containers: IndividualContainer[]) {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get("/docker/containers/" + container.id).then(response => {
// read currently available array of containers into an array
const resp = response.data;
resp["id"] = container.id;
resp["watcher"] = true;
returnArray.push(resp);
});
} else {
returnArray.push(container);
}
// this is inside the forEach callback function not updateContainer function
return returnArray;
});
}
Then you assign void to containers which is supposed to be of type IndividualContainer[] so TypeScript gives you an error then you set that in the state:
updateContainers() {
axios
.get("/docker/containers")
.then(response => {
const apiRequestedContainers: string[] = response.data.containers; // array of only IDs
const stateContainers: IndividualContainer[] = [
...this.state.containers
];
// remove dead containers from state by copying still live containers
let filteredContainers: IndividualContainer[] = [
...this.filterOutContainers(stateContainers, apiRequestedContainers)
];
// add new containers
filteredContainers = this.addContainerToArray(
filteredContainers,
apiRequestedContainers
);
// this return void as well
return this.updateContainer(filteredContainers);
})
// finalArray is void
.then(finalArray => {
// you assign void to containers which should be of type IndividualContainer[]
const newState: CState = { containers: finalArray };
// containers will be set to undefined in you state
this.setState(newState);
});
}
You meant to do this:
// I added a return type here so that TypeScript would yell at me if I return void or wrong type
updateContainer(containers: IndividualContainer[]): IndividualContainer[] {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get("/docker/containers/" + container.id).then(response => {
// read currently available array of containers into an array
const resp = response.data;
resp["id"] = container.id;
resp["watcher"] = true;
returnArray.push(resp);
});
} else {
returnArray.push(container);
}
// removed the return from here as it's useless
});
// you should return the array here
return returnArray;
}
First, I've commented on errors in your code:
updateContainers() {
axios.get('/docker/containers')
.then(response => {
...
return this.updateContainer(filteredContainers);
// returns `undefined`...
})
.then(finalArray => { ... });
// ...so `finalArray` is `undefined` - the reason for TS error
// Also `undefined` is not a `Promise` so this second `then()`
// doesn't make much sense
};
updateContainer(containers: IndividualContainer[]) {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get('/docker/containers/' + container.id)
.then(response => {
...
returnArray.push(resp)
// because `axios.get()` is asynchronous
// this happens only some time after
// `.then(finalArray => { ... })` is finished
});
// at this moment code inside `.then()` has not been executed yet
// and `resp` has not yet been added to `returnArray`
} else {
returnArray.push(container)
// but this happens while `forEach()` is running
}
return returnArray;
// here you return from `forEach()` not from `updateContainer()`
// also `forEach()` always returns `undefined`
// so even `return containers.forEach(...)` won't work
});
// no return statement, that implicitly means `return undefined`
};
Now, why the #RocKhalil's answer, kind of, works:
async updateContainers() {
const response = await axios.get('/docker/containers')
// he favors a much clearer syntax of async/await
...
const containers = await this.updateContainer(filteredContainers)
this.setState({ containers });
};
async updateContainer(containers) {
return containers.map(async (container) => {
if (container.watcher) {
const response = await axios.get('/docker/containers/' + container.id)
// Because `axios.get()` was **awaited**,
// you can be sure that all code after this line
// executed when the request ended
// while this
// axios.get(...).then(() => console.log(2)); console.log(1)
// will lead to output 1 2, not 2 1
return {
...response.data,
id: container.id,
watcher: true,
}
} else {
return container;
}
});
// he does not forget to return the result of `map()`
// and `map()` in contrast with `forEach()` does have a result
// But...
}
But...
containers.map() returns an array. An array of Promises. Not a single Promise. And that means that
const containers = await this.updateContainer(filteredContainers)
waits for nothing. And updateContainer() function is not actually async.
To fix that you need to use Promise.all():
const containers = await Promise.all(this.updateContainer(filteredContainers))

Resources