Using React Native Await/Async in main function [duplicate] - javascript

I am pretty much familiar with the async await but with back end nodejs. But there is a scenario came across to me where I have to use it on front end.
I am getting array of objects and in that objects I am getting lat lng of the places. Now using react-geocode I can get the place name for a single lat lng but I want to use that inside the map function to get the places names. SO as we know it async call I have to use async await over there.
Here is the code
import Geocode from "react-geocode";
render = async() => {
const {
phase,
getCompanyUserRidesData
} = this.props
return (
<div>
<tbody>
await Promise.all(_.get(this.props, 'getCompanyUserRidesData', []).map(async(userRides,index) => {
const address = await Geocode.fromLatLng(22.685131,75.873468)
console.log(address.results[0].formatted_address)
return (
<tr key={index}>
<td>
{address.results[0].formatted_address}
</td>
<td>Goa</td>
<td>asdsad</td>
<td>{_.get(userRides,'driverId.email', '')}</td>
<td>{_.get(userRides,'driverId.mobile', '')}</td>
</tr>
)
}))
</tbody>
</div>
)
}
But when I use async with the map function here it doesn't return anything. Can anyone please help me where I going wrong?

You should always separate concerns like fetching data from concerns like displaying it. Here there's a parent component that fetches the data via AJAX and then conditionally renders a pure functional child component when the data comes in.
class ParentThatFetches extends React.Component {
constructor () {
this.state = {};
}
componentDidMount () {
fetch('/some/async/data')
.then(resp => resp.json())
.then(data => this.setState({data}));
}
render () {
{this.state.data && (
<Child data={this.state.data} />
)}
}
}
const Child = ({data}) => (
<tr>
{data.map((x, i) => (<td key={i}>{x}</td>))}
</tr>
);
I didn't actually run it so their may be some minor errors, and if your data records have unique ids you should use those for the key attribute instead of the array index, but you get the jist.
UPDATE
Same thing but simpler and shorter using hooks:
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const resp = await fetch('some/url');
const json = await resp.json()
updateData(json);
}
getData();
}, []);
return data && <Child data={data} />
}

With the wrapper function below, delayed_render(), you can write asynchronous code inside a React component function:
function delayed_render(async_fun, deps=[]) {
const [output, setOutput] = useState()
useEffect(async () => setOutput(await async_fun()), deps)
return (output === undefined) ? null : output
}
This wrapper performs delayed rendering: it returns null on initial rendering attempt (to skip rendering of this particular component), then asynchronously calculates (useEffect()) the proper rendering output through a given async_fun() and invokes re-rendering to inject the final result to the DOM. The use of this wrapper is as simple as:
function Component(props) {
return delayed_render(async () => { /* any rendering code with awaits... */ })
}
For example:
function Component(props) {
return delayed_render(async () => {
const resp = await fetch(props.targetURL) // await here is OK!
const json = await resp.json()
return <Child data={json} />
})
}
UPDATE: added the deps argument. If your async_fun depends on props or state variables, all of them must be listed in deps to allow re-rendering. Note that passing deps=null (always re-render) is not an option here, because the output is a state variable, too, and would be implicitly included in dependencies, which would cause infinite re-rendering after the async_fun call completes.
This solution was inspired by, and is a generalization of, the Jared Smith's one.

Related

Saving api response to State using useState and Axios (React JS)

I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Try:
setWidgets(data);
istead of
setWidgets(widgets, data);
Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..

Infinite loop in React GET API call and correct way to use Hooks and useState()

i'm new in React and i'm developing an application with standard CRUD operations to a Express.js and MongoDB remote backend.
In a page I need to display values from a GET API call to a remote server, made with Axios. Every object as multiple fields, and the company field (the value in Exhibitor column, for example is something like 5f280eb605c9b25cfeee285c) corresponds to the _id Mongo object field value of another object in another collection.
I need to recover in the table raw the value, make another API call and get the name field (for example Company name example string) from the object with that _id. After that I need to display it in the table fields instead of the _id.
To make it more clear for example the item.company field 5f27e8ee4653de50faeb1784 will be showed as Company name example string.
Also I need to do the same with Status column (but without GET API call to a remote server), where I need to display an icon depending on item.active value, which is boolean.
This needs to be done without any button, but when I open the page automatically.
I've made a standard javascript function to do this but i'm getting an infinite loop, i suppose because React is calling the function every time he renders.
What is the correct way to do this operation?
Here's the errors from the console after the loop
xhr.js:178 GET http://myserver.com/companies/5f280eb605c9b25cfeee285c net::ERR_INSUFFICIENT_RESOURCES
import React, { useState, useEffect, useCallback } from 'react'
import { Tab, Tabs, Col, Form, Button } from 'react-bootstrap'
import { FiTrash, FiCloud, FiPhoneCall, FiUserCheck, FiUserX, FiEye } from 'react-icons/fi'
import axios from 'axios'
const EventProfile = (props) => {
// SOME CODE HERE //
//GET STANDS FROM DB
const [allStands, viewStands] = useState([{}]);
useEffect(() => {
const id = props.match.params.id
const fetchStands = async () => {
const response = await axios.get(`http://myserver.com/stands/${id}`);
viewStands(response.data);
}
fetchStands();
}, [])
// RECOVER NAME USING THE COMPANY ID FROM ANOTHER COLLECTION
const [companyNameGetted, getCompanyName] = useState({})
const getCompanyFromId = useCallback((props) => {
const id = props;
const getCompany = async () => {
const response = await axios.get(`http://myserver.com/companies/${id}`);
getCompanyName(response.data);
}
getCompany([]);
}, [])
// DISPLAY ICON DEPENDING ON OBJECT active FIELD
const handleStandStatus = (status) => {
if(status === true) {
return <FiCloud style={{color: "green"}}/>;
} else {
return <FiCloud style={{color: "grey"}} description="Offline"/>;
}
}
// OTHER CODE HERE //
return (
//SOME CODE HERE//
<Tab eventKey="stands" title="Stands">
<div className="py-12 w-full">
<table className="table table-lg">
<thead>
<tr>
<th>Status</th>
<th>Name</th>
<th>Exhibitor</th>
<th>Size</th>
<th>Color Palette</th>
</tr>
</thead>
<tbody>
{allStands.map((item, index) =>{
return(
<tr key={index}>
<td>{handleStandStatus(item.active)}</td>
<td><Link to={`/standProfile/${item._id}`}>{item.name}</Link></td>
<td>{getCompanyFromId(item.company)}<Link to={`/companyProfile/${item.company}`}><span>{companyNameGetted.name}</span></Link></td>
<td>{item.size}</td>
<td>{item.colorPalette}</td>
</tr>
)
})}
</tbody>
</table>
</div>
</Tab>
// OTHER CODE HERE //
)
}
export default EventProfile
Probably this part is responsible for the infinite loop:
<td>{getCompanyFromId(item.company)}<Link to={`/companyProfile/${item.company}`}><span>{companyNameGetted.name}</span></Link></td>
because you call a function within the return of your component, which the function then will call the getCompany function which will update your companyNameGetted state.
The companyNameGetted state is referenced on your component return , so calling the getCompanyFromId will result in a re-render, that will fetch the company, change the state, re-render, etc, resulting in an infinite loop.
You can fetch the companies within the useEffect after you get all the stands, or you can set a
useEffect(() => {get all company from allStands}, [allStands]);
so it'll reflect on allStands state changes.
Edit: here's an example to further describe what I mean.
const EventProfile = props => {
// usually you'll want to name the variables as so:
// a noun/object for the first one (stands)
// a setter for the second one, since it is a function to set the `stands`
const [stands, setStands] = useState([]);
const [companies, setCompanies] = useState({});
// usual useEffect that'll be triggered on component load, only one time
useEffect(() => {
const fetchStands = async () => {
const response = await axios.get("stands url here");
setStands(response.data);
};
fetchStands();
}, []);
//another useEffect that'll be triggered when there's a change in the dependency array given, i.e. the `stands` variable. so, it'll fetch company names whenever the `stands` state changes.
useEffect(() => {
const fetchCompanies = async () => {
const newCompanies = {...companies};
// wait for all company names have been retrieved
await Promise.all(stands.forEach(s => {
const id = s.company;
const response = await axios.get("company url here with " + id);
newCompanies[id] = response.data;
}));
setCompanies(newCompanies);
};
fetchCompanies();
}, [stands]);
return (
// ... some components
{stands.map((item, index) => (
<tr key={index}>
<td><Link to={`/some/url/${item.company}`}>{companies[item.company]}</Link></td>
</tr>
)}
);
}

asynchronous code to nested synchronous manner in Meteor/React

I have been trying to run data fetching code in a container component and pass it to the display one to reduce using hooks and load time. I tried both await/sync and Meteor's wrapAsync Here is its Docs but I admit I dont really understand this function.
N.B: The code is pseudo code that explains the basic structure and functionality I need to apply, everything has been imported properly. The code structure and order is the same as the actual code. Thats what matters
Current Result
display component errors out as undefined because the data or t1Doc are empty
Container async/await
const Container = withTracker(({ id, ...rest }) => {
const t1Handle = Meteor.subscribe("table1");
const t2Handle = Meteor.subscribe("table2");
const isLoading = !t1Handle.ready() && !t2Handle.ready();
const fetchT1Documents = async () => {
const t1Doc = await t1Collection.findOne(query);
let t2Doc;
const fetchT2Documents = async () => {
t2Doc = await t2Collection.findOne(t1Doc.FK);
}
fetchT2Documents();
parseXmltoJs(t1Doc, (err, result) => {
fetchAjaxData(result,t2Doc)
.then((data) => {
return {
isLoading,
t1Doc,
t2Doc,
data
}
})
});
}
fetchT1Documents();
return {
isLoading,
t1,
...rest,
};
})(WithLoading(displayComponent));
Loader
const WithLoading = (Comp) => ({ isLoading, children, ...props }) => {
if (isLoading) {
return <Loader />;
} else {
return (
<Fade in={true} timeout={500}>
<div>
<Comp {...props}>{children}</Comp>
</div>
</Fade>
);
}
};
export { WithLoading };
sorry for the bad editing
A few tips on this one...
You can make a publication that publishes more than one collection/query, eg table1 and table2. It returns a single handle, that is ready when both collections have data.
No need to do an await on the collection.findOne() calls - these are calls to minimongo (client side cache), and will return either null when the subscription is loading, and will return the data otherwise.
The pattern you are using is excellent, and allows separation of display logic from the grubby data layer :)

React hooks not set state at first time

i have a problem with hooks.
i'm using react-hooks, i have a button which at onClick getting data from api and setState with it.
Problem is:
when i click to button first time i get response from api but can't set it to state. When click to button second time i can setState. Why it happened ?
here is my component look like:
function App() {
const [a, setA] = useState(null);
const fetchData = () => {
let data = {
id: 1
}
axios.post(baseUrl, data)
.then(res => {
if (res.data) {
setA(res.data)
}
console.log(a)
})
}
return (
<div className="App">
<div>
<button onClick={fetchData}></button>
</div>
</div>
);
}
export default App;
i tried to using fetchData function like that:
function fetchData() {
let data = {
id: 1
}
axios.post(baseUrl, data)
.then(res => {
if (res.data) {
setA(res.data)
}
console.log(a)
})
}
but it's not helped too
a is a const. It's impossible to change it, so there's no way your console.log statement at the end of fetchData could ever log out something different than the value that a had when fetchData was created.
So that's not what setA does. The point of setA isn't to change the value of the const, but to cause the component to rerender. On that new render, a new set of variables will be created, and the new a will get the new value. Move your console.log out to the body of your component, and you'll see it rerender with the new value.
In short: Your code appears to be already working, you just have the wrong logging.
If your scope is to fetch data, use this:
const [data, setData] = useState("");
useEffect(async () => {
const result = await axios(
'here will be your api',
);
setData(result.data);
});
Now your response will be stored in data variable.
I would not use an effect for it, effects are useful if the props or state changes and can thereby substitute lifecycle methods like componentDidMount, componentDidUpdate, componentWillUnmount, etc.. But in your case these props haven't changed yet (you want to change your state though). Btw, be aware that #Asking's approach will fetch data on EVERY rerender (props or state change). If you want to use useEffect, be sure to add the second parameter to tell React when to update.
Normally, your code should work, I haven't tested but looks legit. Have you used developer tools for react to check if the state/hook has changed? Because if you say it did not work because of the console.log printing: Have in mind that setA() is an async function. The state was most likely not yet changed when you try to print it. If you haven't react developer tools (which I highly recommend) you can check the real state by adding this code in the function:
useEffect(() => console.log(a), [a]);
I have a few real improvements to your code though:
function App() {
const [a, setA] = useState(null);
const fetchData = useCallback(async () => {
let data = {
id: 1
}
const res = await axios.post(baseUrl, data);
setA(res.data);
}, []);
return (
<div className="App">
<div>
<button onClick={fetchData}></button>
</div>
</div>
);
}
export default App;
By adding useCallback you ensure that the function is memorized and not declared again and again on every rerender.
#Nicholas Tower has already answered your question. But if you are looking for code
function App() {
const [a, setA] = useState(null);
const fetchData = () => {
let data = {
id: 1
}
axios.post(baseUrl, data)
.then(res => {
if (res.data) {
setA(res.data)
}
})
}
console.log(a)
return (
<div className="App">
<div>
<button onClick={fetchData}></button>
</div>
</div>
);
}
export default App;
just place log before return (. This should do

How do I pass a value from a promise to a component prop in react native?

Edit: I don't understand the reason for downvotes, this was a good question and no other questions on this site solved my issue. I simply preloaded the data to solve my issue but that still doesn't solve the problem without using functional components.
I'm trying to pass users last message into the ListItem subtitle prop but I can't seem to find a way to return the value from the promise/then call. It's returning a promise instead of the value which gives me a "failed prop type". I thought about using a state but then I don't think I could call the function inside the ListItem component anymore.
getMsg = id => {
const m = fireStoreDB
.getUserLastMessage(fireStoreDB.getUID, id)
.then(msg => {
return msg;
});
return m;
};
renderItem = ({ item }) => (
<ListItem
onPress={() => {
this.props.navigation.navigate('Chat', {
userTo: item.id,
UserToUsername: item.username
});
}}
title={item.username}
subtitle={this.getMsg(item.id)} // failed prop type
bottomDivider
chevron
/>
);
You could only do it that way if ListItem expected to see a promise for its subtitle property, which I'm guessing it doesn't. ;-) (Guessing because I haven't played with React Native yet. React, but not React Native.)
Instead, the component will need to have two states:
The subtitle isn't loaded yet
The subtitle is loaded
...and render each of those states. If you don't want the component to have state, then you need to handle the async query in the parent component and only render this component when you have the information it needs.
If the 'last message' is something specific to only the ListItem component and not something you have on hand already, you might want to let the list item make the network request on its own. I would move the function inside ListItem. You'll need to set up some state to hold this value and possibly do some conditional rendering. Then you'll need to call this function when the component is mounted. I'm assuming you're using functional components, so useEffect() should help you out here:
//put this is a library of custom hooks you may want to use
// this in other places
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
const ListItem = ({
title,
bottomDivider,
chevron,
onPress,
id, //hae to pass id to ListItem
}) => {
const [lastMessage, setLastMessage] = useState(null);
const isMounted = useIsMounted();
React.useEffect(() => {
async function get() {
const m = await fireStoreDB.getUserLastMessage(
fireStoreDB.getUID,
id
);
//before setting state check if component is still mounted
if (isMounted.current) {
setLastMessage(m);
}
}
get();
}, [id, isMounted]);
return lastMessage ? <Text>DO SOMETHING</Text> : null;
};
I fixed the issue by using that promise method inside another promise method that I had on componentDidMount and added user's last message as an extra field for all users. That way I have all users info in one state to populate the ListItem.
componentDidMount() {
fireStoreDB
.getAllUsersExceptCurrent()
.then(users =>
Promise.all(
users.map(({ id, username }) =>
fireStoreDB
.getUserLastMessage(fireStoreDB.getUID, id)
.then(message => ({ id, username, message }))
)
)
)
.then(usersInfo => {
this.setState({ usersInfo });
});
}
renderItem = ({ item }) => (
<ListItem
onPress={() => {
this.props.navigation.navigate('Chat', {
userTo: item.id,
UserToUsername: item.username
});
}}
title={item.username}
subtitle={item.message}
bottomDivider
chevron
/>
);

Categories

Resources