get data from promise - javascript

I have some rest api, and need to get data from promise and render it.
const data = http.get(url) // it return the promise.
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: Array(4)
I can console log needed data:
const data = http.get(url).then(res=> console.log(res.data)) // array in console
I read some tutorials, and allways, always it shows how to console log results, but I dont need it in concole, I need render it in component. It may be simple question, but im total new in JS.
const ItemsList = () => {
const data = http.get(url) //return promise. How can I get array from thid promise result?
return (
<ul>
{ data.map(item => {
return (
<ItemPreview key={item.id} item={item} />
);
})}
</ul>
)
}
export default ItemsList
tried async/await. spend already 4 hours to this task, but get only errors. Just don't kwnow how to write it. can someone help, and show me how to do it. Or give me link where I can find how to render promise result in component?

async/await is the way to go
const ItemsList = async () => {
const data = await http.get(url)
return (
<ul>
{ data.map(item => {
return (
<ItemPreview key={item.id} item={item} />
);
})}
</ul>
)
}

Related

React UseEffect Updating State but no Rerender

I have an extremely simple issue in which I would like to load input devices and list them. The Web API for Media Devices shows it returns a promise, so I assumed I would have to load this into the state with a useEffect hook.
React Developer tools shows a list of strings in the inputs array, although the state does not update.
const App = () => {
const [inputs, setInputs] = useState([]);
useEffect(() => {
let inputDevices = [];
navigator.mediaDevices.enumerateDevices().then((devices) => {
devices.forEach((device) => inputDevices.push(device.label));
});
setInputs([inputDevices]);
}, []);
return (
<div>
<ol>
{inputs.map((device, index) => {
return (
<li key={index}>
id:{index} - Device: {device}{" "}
</li>
);
})}
</ol>
</div>
);
output:
edit: the setinputs([inputDevices]) was an accident, thank you all who reminded me about the asynchronicity of the promise :)
The issue is setInputs will be execute before the then callback, so you need to setInputs inside the then callback, and you can use map to get the list of labels.
let inputDevices = [];
navigator.mediaDevices.enumerateDevices().then((devices) => {
setInputs(devices.map((device) => device.label))
});
let inputDevices = [];
navigator.mediaDevices.enumerateDevices().then((devices) => {
devices.forEach((device) => inputDevices.push(device.label));
});
setInputs([inputDevices]);
You need to call set state inside then. Promises are resolved asynchronously hence when you call setInputs where you have it by that time data isn't there yet.
You can also just utilize map:
setInputs(devices.map((device) => device.label)) // inside .then

How do I map data from state to component prop?

To start with I'm a beginner. Any help would be appreciated.
So I'm getting my data from mongoDB atlas using node+express API. I'm successfull at getting the array to show up in console log using following code.
const [product, setProduct] = useState();
const url = "http://localhost:5000/api/items";
useEffect(() => {
axios.get(url).then((res) => {
setProduct(res.data);
// setProduct(
// JSON.stringify({
// title: setProduct.title,
// price: setProduct.price,
// image: setProduct.image,
// details: setProduct.details,
// })
// );
})
}, [url])
console.log(product)
The console log displays the array properly as collection named 'items' with content of arrays. As you can see I tried to stringify the response as the response returns JSON but again I didn't know how to map Following is the code where I tried to map the contents like id, name etc as props to component.
<div>
{product.map((product) => {
<Product name={product.title} />
})}
</div>
When I do this I get error that the map is not a function. I don't know what I'm doing wrong here. I know I'm supposed to use redux or reducer/context here but I want to get this to work before updating it with those.
[![Response from res.data][1]][1]
[1]: https://i.stack.imgur.com/auxvl.png
you didnt get yours products.
As we can see from screenshot
res.data equal to object with one property items:
res.data= {items: []}
and we need to take/access to these items
use this: setProducts(res?.data?.items || [])
const [products, setProducts] = useState();
useEffect(() => {
axios.get(url).then((res) => {
setProducts(res?.data?.items || []);
})
}, [url])
<div>
{products?.map((product) => {
<Product name={product.title} />
})}
</div>
On the first render the value for types will be undefined ( on sync code execution ), try using it as
<div>
{product?.map((product) => {
<Product name={product.name} />
})}
</div>
? will make sure to run map once value for types is there ( also make sure it is mappable ( is an array ))

How return url for a img tag with a function

so I'm rendering out an array of posts. Each post has a unique user id associated with it, which I'm trying to use in another API call to return the user's image for that post.
Currently i have it setup like so
Rendering out the post
{data?.map((data) => (
<div className="allpostsingle" key={data.id} onClick={() => sendTo(data.id)}>
<div className="allpostheader">
<img src={getUrl(data.user)}/>
<p>{data?.description}</p>
</div>
</div>
))}
The function to get the image URL
const getUrl = async (id) => {
let url = `http://127.0.0.1:8000/api/user/${id}/`
const response = await fetch(url)
const data = await response.json()
const avatarurl = data.avatar
return avatarurl
}
The issue with this is it returns a promise instead of the URL. I know you can set it in a state to get the url, but then it wouldnt be unique to each post?
Im just wondering how i can solve this or if theres a different way to solve this issue.
It doesn't work because rendering in react in synchronous while your getUrl is an async function. As some other answers pointed out, you can fetch all url in componentDidMount or useEffect hook and update the state.
If you don't want to change your existing code, you can also replace img with a react component of your own so it can handle promise.
function Image({src}){
const [actualSrc, setActualSrc] = useState('placeholder image link')
useEffect(()=>{
src.then((url) => setActualSrc(url));
}, [src]);
return <img src={actualSrc}/>
}
{data?.map((data) => (
<div className="allpostsingle" key={data.id} onClick={() => sendTo(data.id)}>
<div className="allpostheader">
<Image src={getUrl(data.user)}/>
<p>{data?.description}</p>
</div>
</div>
))}
const getUrl = async (id) => {
let url = `http://127.0.0.1:8000/api/user/${id}/`
const response = await fetch(url)
const data = await response.json()
const avatarurl = data.avatar
return avatarurl
}
This may not be best for performance as getUrl will send a new reference of promise on each render. In this case, best would be to pass id (user id in getUrl) to the Image component instead of a promise and have logic of fetching the actualUrl inside of it.
function Image({userId}){
const [actualSrc, setActualSrc] = useState('placeholder image link')
useEffect(()=>{
const getUrl = async (id) => {
let url = `http://127.0.0.1:8000/api/user/${id}/`
const response = await fetch(url)
const data = await response.json()
const avatarurl = data.avatar
return avatarurl;
getUrl(userId).then((url) => setActualSrc(url));
}, [userId]);
return <img src={actualSrc}/>
}
{data?.map((data) => (
<div className="allpostsingle" key={data.id} onClick={() => sendTo(data.id)}>
<div className="allpostheader">
<Image userId={data.user} />
<p>{data?.description}</p>
</div>
</div>
))}
You can't assign the return value of getUrl() to the src attribute because it is asynchronous and returns a Promise.
What you can do instead is maintain another piece of state for the user avatars that updates when your data does. For example, create a function that resolves user avatars
const resolveUserAvatars = async (users) => {
const avatars = await Promise.allSettled(users.map(async ({ user }) => {
const res = await fetch(`http://127.0.0.1:8000/api/user/${encodeURIComponent(user)}/`)
if (!res.ok) {
throw new Error(res.status)
}
return {
user,
avatar: (await res.json()).avatar
}
}))
return Object.fromEntries(
avatars
.filter(({ status }) => status === "fulfilled")
.map(({ value: { user, avatar } }) => [ user, avatar ])
)
)
}
Then in your component...
const [ data, setData ] = useState([]) // init as an array
const [ avatars, setAvatars ] = useState({})
// avatars will look like
// { userId: imageUrl, ... }
useEffect(() => {
resolveUserAvatars.then(setAvatars)
}, [ data ]) // execute when data changes
and in your JSX
{data.map(({ id, user, description }) => (
<div className="allpostsingle" key={id} onClick={() => sendTo(id)}>
<div className="allpostheader">
<img src={avatars[user] ?? "some/placeholder.png"}/>
<p>{description}</p>
</div>
</div>
))}
See also Promise.allSettled()
It's not a good idea to get each user's avatar separately and send request for each post and every time render post items, If it's possible for you and the backend api just include users avatar url in post so easily render it.
if its not possible and have a limitation in api you just need to get all post user data with Promiss.allSettled in useEffect after the post data has recieved and attach user data to postItem or keep it in separate dictionary then set the state to render the post and user avatar url in it.
As you have said that your function returns a promise instead of URL (I had the same issue). This issue can happen to any asynchronous function when they are called as usual function (they will return a promise instead of an output), unless you use callback functions or async/await functions in a usual JavaScript code. But the in React, asynchronous functions should be called using UsEffect() hook OR componentDidMount() function. You need to refer to How to call an async function inside a UseEffect() in React? if your react component is a function. Or refer How to call an async function in componentDidMount function? if your react component is a class to know how exactly use them in react to handle asynchronous functions in React components.

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 :)

Can't map over constructed array

Having some trouble mapping over this array to create elements. What I'm doing is fairly simple. I'm querying my database which returns and title and a URL. I then send that URL out to fetch the html from a website. I go through that response and pull out a bunch of json objects. Those objects are pushed to an array. Then I want to map through that array to post images to my screen. What I have read online is that you can't map over a constructed array. But if I'm not mistaken I'm doing that once and it works fine. But when it comes to pulling the variables from the array and using it to produce the img it doesn't work.
class Scraper extends Component {
arraystuff = [];
handleData = (data) => {
data.allSearches.map((datum) => {
// This is constructing the results array.
this.results.push({ title: datum.title, url: datum.url });
});
// Now I'm mapping over results without issue
this.results.map(async (post) => {
await fetch(
`http://localhost:4100/scrape/${encodeURIComponent(post.url)}`
)
.then((res) => res.json())
// Now I am constructing the arraystuff array. I cannot map over this in the render
.then((res) => res.map((result) => this.arraystuff.push(result)));
});
};
render() {
return (
<>
<Query query={ALL_SEARCHES_QUERY}>
{({ error, loading, data }) => {
if (error) return <p>Error!</p>;
if (loading) return <p>Loading...</p>;
this.handleData(data);
return null;
}}
</Query>
<div>
{this.arraystuff.map((search) => {
// This is what isn't working
<img
src={`http:${search.photo}`}
alt={search.id}
key={search.id}
/>;
})}
</div>
</>
);
}
}
export default Scraper;
Any help with this would be greatly appreciated. Doesn't seem like it should be that difficult. Driving me nuts

Categories

Resources