Why my axios get not always working in React - javascript

I try to fetch data from blogger api.
const NajnowszeWpisy = () => {
const [articles, setArticles] = useState([]);
useEffect(() => {
axios
.get(
'https://www.googleapis.com/blogger/v3/blogs/****/posts?key=****'
)
.then((res) => {
setArticles(res.data.items);
});
});
Then check if there are any articles (in my blog).
Check if there is any image in blog content, if yes, convey it to props.
if (articles.length) {
console.log('get');
let image = '';
const lastPost = articles[articles.length - 1];
if (lastPost.content.match(/<img\s+[^>]*src="([^"]*)"[^>]*>/i)) {
image = ReactHtmlParser(
lastPost.content.match(/<img\s+[^>]*src="([^"]*)"[^>]*>/i)[0]
);
}
return (
<StyledContainer>
<Post
image={
image ? (
image
) : (
<img src={defaultImage} alt="default-img" />
)
}
title={lastPost.title ? lastPost.title : 'Nowy Artykuł'}
content={lastPost.content}
id={lastPost.id}
key={lastPost.id}
/>
</StyledContainer>
);
} else {
console.log('no get');
}
};
In response i get error GET 429, so there are too many requests.
Also I can't get any of my console.log messages ('get' or 'no get').
Can you tell me if I missed something?

You should try to pass an empty array as second argument to useEffect to run your function only once after initial render:
useEffect(() => {
axios
.get(
'https://www.googleapis.com/blogger/v3/blogs/****/posts?key=****'
)
.then((res) => {
setArticles(res.data.items);
});
}, []);
From React docs:
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.

Related

In React, how can I make a single api call, set the result to a state variable, and display it?

I am programming a react application where I need to make a single async api call, save the result to a state variable and display the result. I am using an axios instance where the response of the api call is a nested object. For example:
{
kanji:
{character: "", stroke:""},
quote: "string"
}
So far I have the following code where I am able to console.log the homeData object successfully. However I get the error: 'TypeError: homeData is undefined'.
const Home = () =>{
const [homeData, setHomeData] = useState({})
const getHomeData = async()=>{
instance.get("/data")
.then(res =>{
setHomeData(res.data)
})
.catch(error =>{
console.log(error)
})
}
useEffect(()=>{
getHomeData()
}, [])
console.log(homeData)
return(
<>
<p>{homeData}<p>
</>
)
}
This seems like a promise issue but I am not sure how to fix it. Thank you for your help in advance.
This is not a promise issue, it has to due with the order in which the lifecycle methods run. useEffect runs after the component renders. This means when the component first renders, the homeData is an empty object. homeData will be present in the second render. The following code first checks if homeData exists, then if it exists, it displays the kanji character. Note also, you cant just display a raw object into the dom so you will have to access the homeData by property for it to display
const Home = () =>{
const [homeData, setHomeData] = useState({})
const getHomeData = async()=>{
instance.get("/data")
.then(res =>{
setHomeData(res.data)
})
.catch(error =>{
console.log(error)
})
}
useEffect(()=>{
getHomeData()
}, [])
console.log(homeData)
return(
<>
<p>{homeData && homeData.kanji.character}<p>
</>
)
}

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..

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

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.

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

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