How to update state after an async call inside a map function? - javascript

I'm trying to get a data that is returning from axios get method to a method call on aon object. Instead of returning the value, it's returning the promise. What am I missing here?
import React, { Component } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
class App extends Component {
state = {
totalResults: ""
};
componentDidMount() {
this.setState({
totalResults: this.fetchData()
.then(function(res) {
const r = res.data.totalResults;
return r;
})
.catch(err => console.log("error: ", err))
});
}
fetchData = () => {
return axios.get(
"https://newsapi.org/v2/top-headlines?country=us&apiKey=8d98dac05ec947d1b891832495641b49"
);
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => console.log(this.state.totalResults)}>
Click
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here's the link to codesandbox
Note: This is just a reference code. I can't do setState because I'm trying to call the method from array.map() iterator instead.
Edit:
This is what actually I'm trying to do: codesandbox.io/s/n5m3qyn65l
For some reason it's showing Network Error
Thanks for helping me out.

You should use Promise.all() to fetch all images before updating your articles. Like so:
const res = response.data;
Promise.all(res.map(article => (
this.fetchImages(article.featured_media)
.then(image => ({
title: article.title.rendered,
content: article.content.rendered,
featuredImage: image.guid.rendered
}))
.catch(err => console.log("Error fetching image: ", err))
))
)
.then(articles => this.setState({ articles }))
.catch(err => console.log("Error setting up articles: ", err))

You should setState after getting the response from fetchData() because fetchData() will return promise, which you're setting in the state.
Remember, setState will do assignment only, you can't expect it wait for asynchronous operation. In that case, try async/await
Updated answer:
https://codesandbox.io/s/1r22oo804q
Find the inline comments

// Our basic components state setup
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
currentSession: {
fullNames: [],
contactInfo: []
}
}}
// Here we make our function to fetch our API data
fetchData()
.then(res => {
let namesData = res.data.name;
let contactData = res.data.email;
this.setState(prevState => ({
currentSession: {
...prevState.currentSession,
fullNames: Object.assign(namesData),
contactInfo: Object.assign(contactData)
}
}));
});
// Since it's an async call, we'll put in in a 'componentDidMount' method
componentDidMount() {
this.fetchData();
}

Related

React JS Creating a reusable component to get API Data

I'm trying to create a component that allows me to access API data anywhere I import it.
I have a GetTeamsByLeague component which looks like this.
import axios from 'axios';
const GetTeamsByLeague = (league: string) => {
const teams = axios
.get(`http://awaydays-api.cb/api/teams/${league}`)
.then((response: any) => {
return response;
})
.catch((error: any) => {
console.log(error);
});
return teams;
};
export default GetTeamsByLeague;
Then in my app component, I have this
import Header from './Components/Header/Header';
import GetTeamsByLeague from './Hooks/GetTeamsByLeague';
import './Reset.scss';
function App() {
const championshipTeams = GetTeamsByLeague('Championship');
console.log(championshipTeams);
return (
<div className='App'>
<Header/>
</div>
);
}
export default App;
The issue is the console.log() just return the promise not the data.
If I use useState() in my GetTeamsByLeague component like this
import axios from 'axios';
import { useState } from 'react';
const GetTeamsByLeague = (league: string) => {
const [teams, setTeams] = useState({});
axios
.get(`http://awaydays-api.cb/api/teams/${league}`)
.then((response: any) => {
setTeams(response.data);
})
.catch((error: any) => {
console.log(error);
});
return teams;
};
export default GetTeamsByLeague;
Then I get the following errors
(3) [{…}, {…}, {…}] ...
GET http://awaydays-api.cb/api/teams/Championship 429 (Too Many Requests)
/* EDIT */
I've now updated my GetTeamsByLeague component too
import axios from 'axios';
import { useEffect, useState } from 'react';
interface Teams {
id: number;
name: string;
league: string;
created_at: string;
updated_at: string;
}
const useGetTeamsByLeague = (league: string) => {
const [teams, setTeams] = useState<Teams[]>();
useEffect(() => {
axios
.get(`http://awaydays-api.cb/api/teams/${league}`)
.then((response: any) => {
setTeams(response.data);
})
.catch((error: any) => {
console.log(error);
});
}, [league, setTeams]);
return teams;
};
export default useGetTeamsByLeague;
In my component done
import useGetTeamsByLeague from './Hooks/useGetTeamsByLeague';
import './Reset.scss';
function App() {
const teams = useGetTeamsByLeague('Championship');
console.log(teams);
return (
<div className='App'>
<Header>
{teams.map(team => {
<li>{team.name}</li>;
})}
</Header>
</div>
);
}
export default App;
But now I get TypeScript error Object is possibly 'undefined'
The console.log shows empty array first then the data
undefined
(3) [{…}, {…}, {…}]
In the first case, you return a Promise since you don't await the axios fetch. In the second case, after the axios fetch succeeds, you store the result in the state which re-renders the component, which causes an infinite loop of fetching -> setting state -> re-rendering -> fetching [...].
This is a perfect use case for a React hook. You could do something like this:
const useGetTeamsByLeague = (league) => {
const [status, setStatus] = useState('idle');
const [teams, setTeams] = useState([]);
useEffect(() => {
if (!league) return;
const fetchData = async () => {
setStatus('fetching');
const {data} = await axios.get(
`http://awaydays-api.cb/api/teams/${league}`);
if(data && Array.isArray(data)){
setTeams(data);
}
setStatus('fetched');
};
fetchData();
}, [league]);
return { status, teams };
};
And then inside your components do
const {status, teams} = useGetTeamsByLeague("someLeague");
Ofc. you should modify the hook to your needs since I don't know how your data structure etc. looks like.
A component function is run on every render cycle, therefore the request is many times. You should use the useEffect() hook (documentation).
Wrapping this logic in a component is probably not the right tool to use in this case. You should probably consider a custom hook instead, for example:
const useGetTeamsByLeague = (league: string) => {
const [teams, setTeams] = useState({});
useEffect(() => {
axios
.get(`http://awaydays-api.cb/api/teams/${league}`)
.then((response: any) => {
setTeams(response.data);
})
.catch((error: any) => {
console.log(error);
});
}, [league, setTeams]);
return teams;
};

multiple API calls in one componentDidMount using Axios in react

Here is my React js code for a single API call for a date range picker. now I want to call multiple API in React with componentDidMount Method is it possible if yes how can do that
import React,{ Component} from "react";
import axios from 'axios'
class PostList extends Component{
constructor(props) {
super(props)
this.state = {
posts: []
}
}
componentDidMount(){
axios.get('http://127.0.0.1:8000/betweendays')
.then(response => {
this.setState({
posts:response.data
})
console.log(response.data)
})
}
render() {
const {posts} = this.state
return (
<div>
<h1>get call in React js</h1>
{
posts.map(post => <div key = {post.id}>{post.id} </div>)
}
</div>
)
}
}
export default PostList```
Using .then() method to create chain of the requests..
componentDidMount() {
axios.get('http://127.0.0.1:8000/betweendays')
.then(response => {
this.setState({
posts: response.data
})
return response.data; // returning response
})
.then(res => {
// do another request Note we have the result from the above
// getting response returned before
console.log(res);
})
// Tag on .then here
.catch(error => console.log(error))
}
You can add more apis in componentDidMount as u want.
componentDidMount(){
axios.get('http://127.0.0.1:8000/betweendays')
.then(response => {
this.setState({
posts:response.data
})
console.log(response.data)
})
axios.get('link')
.then(response => {
this.setState({
posts:response.data
})
console.log(response.data)
})
}

Waiting for async function in React component & Showing Spinner

Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};

Async Action Redux Unhandled Rejection (Error): Actions must be plain objects. Use custom middleware for async actions

I have a react project that is using redux-thunk. I created an action that will hit an endpoint, then set store to data received. Currently, I am using .then but when I call the action in the componentdidmount, the data is not there. The component renders before the data is available. To fix this, I decided to turn my action into an async action and then await in my componentdidmount. The problem is, as soon as I put async in my action, I get this error....
Unhandled Rejection (Error): Actions must be plain objects. Use custom middleware for async actions.
Here is my code
Action
export const getCasesSuccess = async (data) => {
return {
type: GET_ALL_CASES,
data
}
};
export const getAllCases = () => {
return (dispatch) => {
axios.get('https://corona.lmao.ninja/all')
.then(res => {
const cases = res.data
dispatch(getCasesSuccess(cases))
})
.catch(error => {
throw(error)
})
}
}
Component where action is called
import React from "react";
import { connect } from "react-redux";
import { getAllCases } from "../../store/actions/index";
import AllCases from '../../components/allcases/allCases';
class DataContainer extends React.Component {
constructor(props) {
super(props);
this.state = { }
}
componentDidMount = async () => {
await this.props.getAllCases()
}
render() {
return (
<div>
<AllCases allCases={this.props.allCases} />
</div>
);
}
}
const mapStateToProps = (state) => (
{
allCases: state.allCases
}
)
const mapDispatchToProps = dispatch => {
return {
getAllCases: () => dispatch(getAllCases()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DataContainer);
Remove the async from componentDidmount and use the async and await in getAllCases method
export const getAllCases = async () => {
return (dispatch) => {
await axios.get('https://corona.lmao.ninja/all')
.then(res => {
const cases = res.data
dispatch(getCasesSuccess(cases))
})
.catch(error => {
throw(error)
})
}
}
As the error messages says, Redux actions must be plain objects. Since you're using thunk middleware, you can dispatch functions. But you're returning a promise. Since the data loading is asynchronous, your component should check if the data exists and if it doesn't, render a loading indicator or something. In your reducer, you can set a default state for allCases to null which the DataContainer component will use when the component mounts.
export const getCasesSuccess = (data) => {
return {
type: GET_ALL_CASES,
data
}
};
import React from "react";
import { connect } from "react-redux";
import { getAllCases } from "../../store/actions/index";
import AllCases from '../../components/allcases/allCases';
class DataContainer extends React.Component {
componentDidMount() {
this.props.getAllCases()
}
render() {
const { allCases } = this.props
if (!allCases) {
return <div>Loading...</div>
}
return (
<div>
<AllCases allCases={this.props.allCases} />
</div>
);
}
}
const mapStateToProps = (state) => ({
allCases: state.allCases
})
const mapDispatchToProps = {
getAllCases,
}
export default connect(mapStateToProps, mapDispatchToProps)(DataContainer);

Thunk Action Body not being Invoked

My thunk action doesn't seem to be running through its core logic. I tall the thunk action from componentDidMount but it doesn't in turn cause this to run: const response = await findOne(id).
Also, I thought I didn't need to explicitely pass dispatch as a prop to mapDispatchToProps if using redux-thunk, I thought that the way I have my thunk setup is that dispatch is available already to the thunk? And I've used other actions like this and it's worked fine, why not this one?
Thunk Action
export function fetchCompany(id) {
return async (dispatch) => {
try {
const response = await findOne(id)
if(response && response.body) {
const company = response.body
dispatch(companyReceived(company))
}
} catch(err) {
console.log("failed request in authenticate thunk action")
console.log(`error details: ${err.status} /n ${err}`)
}
}
}
Container
......
import { fetchCompany } from '../../client/actions/company/CompanyAsyncActions'
class InterviewContainer extends Component {
async componentDidMount() {
await fetchCompany(this.props.params.companyId)
}
render(){
return (this.props.company && <Interview className='ft-interview' company={this.props.company} />)
}
}
const mapStateToProps = state => ({
company: state.company.company
})
const mapDispatchToProps = {
fetchCompany: fetchCompany
}
export default connect(mapStateToProps, mapDispatchToProps)(InterviewContainer)
In the past, I haven't passed (dispatch) as a prop to mapDispatchToProps and it worked fine. But I see everyone else is doing so. How was my code working in the past if I wasn't doing that? And why isn't this working this time around in the example above?
Taking a look at another async action thunk container and call example, this is working completely fine, and I'm calling it the same way in another container
container
class HomePageContainer extends Component {
constructor(){
super()
}
async componentDidMount() {
await this.props.fetchFeaturedCompanies()
await this.props.fetchCompanies()
await this.props.fetchCountries()
}
render(){
return (<HomePage className='ft-homepage'
featuredCompanies={this.props.featuredCompanies}
countries={this.props.countries}
companies={this.props.companies}
/>)
}
}
const mapStateToProps = state => ({
countries: state.country.countries,
companies: state.company.companies,
featuredCompanies: state.company.featuredCompanies
})
const mapDispatchToProps = {
fetchCountries: fetchCountries,
fetchCompanies: fetchCompanies,
fetchFeaturedCompanies: fetchFeaturedCompanies
}
export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer)
thunk action
export function fetchCompanies() {
return async (dispatch, getState) => {
const response = await find()
if(response && response.body) {
const companies = response.body
dispatch(companiesReceived(companies))
}
}
}
In componentDidMount of InterviewContainer you're accidentally calling the imported fetchCompany, instead of this.props.fetchCompany.

Categories