I'm working on this app that takes data from a movies API and I want to work with it.
I have this function that gets the API data:
/** #format */
const fetchMovie = movie => {
var APIKEY = "xxxxxxxxxxxxxxxxxxxxxx";
var API2 =
"https://api.themoviedb.org/3/search/movie?api_key=xxxxxxxxxx&language=en-US&page=1&include_adult=false&query=avengers";
var API = `https://api.themoviedb.org/3/search/movie?api_key=${APIKEY}&language=en-US&page=1&query=${movie}`;
fetch(API2)
.then(data => data.json())
.then(movies => console.log(movies) || movies.items)
.catch(error => {
console.log(error);
return null;
});
};
export default fetchMovie;
And I have this App class that uses the API data:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeMovie: "Avengers",
loading: true,
allMovies: []
};
}
componentDidMount() {
this.getData(this.activeMovie);
}
componentDidUpdate(prevState) {
if (prevState.activeMovie !== this.state.activeMovie) {
this.getData(this.state.activeMovie);
}
}
getData(movie) {
this.setState({
loading: true
});
fetchMovie(movie).then(data => {
this.setState({
allMovies: data,
loading: false
});
});
}
Now, before this I have used the same methodology and it worked but I don't know why the I get
TypeError: Object(...)(...) is undefined // this line fetchMovie(movie).then(data => {
The API is good, I can console log the data before it gets to the App component, but the function in the app component somehow doesn't work. any clues?
That's simply because your function fetchMovie() doesn't return a Promise so that you than use .then() after it. You can return a promise instead. However the logic in your code is probably a bit shaky. You might as well look that up because it goes into an infinite loop, consider debugging component life cycles for that.
To return a promise from your function, you can use a similar approach as I wrote in here: https://codesandbox.io/s/small-sun-sfcyv.
You are not returning any promise from your fetchMovie function, that way you can't use the .then so right now you only have access to that data in your fetchMovie. A possible solution would be defining your function as async and then you would be able to return your data from that function.
Try this.
/** #format */
const fetchMovie = movie => {
var APIKEY = "xxxxxxxxxxxxxxxxxxxxxx";
var API2 =
"https://api.themoviedb.org/3/search/movie?api_key=xxxxxxxxxx&language=en-US&page=1&include_adult=false&query=avengers";
var API = `https://api.themoviedb.org/3/search/movie?api_key=${APIKEY}&language=en-US&page=1&query=${movie}`;
return fetch(API2)
.then(data => data.json())
.then(movies => console.log(movies) || movies.items)
.catch(error => {
console.log(error);
return null;
});
};
export default fetchMovie;
Related
I'm trying to put some data into state in a React app. The flow involves fetching a list of IDs from the HackerNews API, then taking each ID and making an additional API call to fetch the item associated with each ID. I ultimately want to have an array of 50 items in my component state (the resulting value of the each '2nd-level' fetch.
When I setState from JUST the single 'top-level' promise/API call, it works fine and my state is set with an array of IDs. When I include a second .then() API call and try to map over a series of subsequent API calls, my state gets set with unresolved Promises, then the fetch() calls are made.
I'm sure this a problem with my poor grasp on building appropriate async methods.
Can someone help me figure out what I'm doing wrong, and what the best practice for this is??
My component:
import React from 'react'
import { fetchStoryList } from '../utils/api'
export default class Stories extends React.Component {
state = {
storyType: 'top',
storyList: null,
error: null,
}
componentDidMount () {
let { storyType } = this.state
fetchStoryList(storyType)
.then((data) => {
console.log("data", data)
this.setState({ storyList: data })
})
.catch((error) => {
console.warn('Error fetching stories: ', error)
this.setState({
error: `There was an error fetching the stories.`
})
})
}
render() {
return (
<pre>{JSON.stringify(this.state.storyList)}</pre>
)
}
}
My API Interface:
// HackerNews API Interface
function fetchStoryIds (type = 'top') {
const endpoint = `https://hacker-news.firebaseio.com/v0/${type}stories.json`
return fetch(endpoint)
.then((res) => res.json())
.then((storyIds) => {
if(storyIds === null) {
throw new Error(`Cannot fetch ${type} story IDs`)
}
return storyIds
})
}
function fetchItemById(id) {
const endpoint = `https://hacker-news.firebaseio.com/v0/item/${id}.json`
return fetch(endpoint)
.then((res) => res.json())
.then((item) => item)
}
export function fetchStoryList (type) {
return fetchStoryIds(type)
.then((idList) => idList.slice(0,50))
.then((idList) => {
return idList.map((id) => {
return fetchItemById(id)
})
})
//ABOVE CODE WORKS WHEN I COMMENT OUT THE SECOND THEN STATEMENT
You are not waiting for some asynchronous code to "finish"
i.e.
.then((idList) => {
return idList.map((id) => {
return fetchItemById(id)
})
})
returns returns an array of promises that you are not waiting for
To fix, use Promise.all
(also cleaned up code removing redundancies)
function fetchStoryIds (type = 'top') {
const endpoint = `https://hacker-news.firebaseio.com/v0/${type}stories.json`;
return fetch(endpoint)
.then((res) => res.json())
.then((storyIds) => {
if(storyIds === null) {
throw new Error(`Cannot fetch ${type} story IDs`);
}
return storyIds;
});
}
function fetchItemById(id) {
const endpoint = `https://hacker-news.firebaseio.com/v0/item/${id}.json`
return fetch(endpoint)
.then(res => res.json());
}
export function fetchStoryList (type) {
return fetchStoryIds(type)
.then(idList => Promise.all(idList.slice(0,50).map(id => fetchItemById(id)));
}
One solution would be to update fetchStoryList() so that the final .then() returns a promise that is resolved after all promises in the mapped array (ie from idList.map(..)) are resolved.
This can be achieved with Promise.all(). Promise.all() take an array as an input, and will complete after all promises in the supplied array have successfully completed:
export function fetchStoryList(type) {
return fetchStoryIds(type)
.then((idList) => idList.slice(0,50))
.then((idList) => {
/* Pass array of promises from map to Promise.all() */
return Promise.all(idList.map((id) => {
return fetchItemById(id)
});
});
}
I'm trying to fetch data by creating a function. In that function I am doing trying to set state and I am calling it from the componentDidMount method, but I am having a few problems:
I am not sure if while is good practice to be used, because I am looping and changing my endpoint so I can get new data every time.
I have tried to return data from the fetching function and use setState inside componentDidMount, but I had a problem, I suspect because componentDidMount is running before fetching has completed
I have tried to use res.json() on the data using a promise, but I got an error that res.json is not a function.
state = {
title: [],
image: [],
rating: [],
};
getData = () => {
let i = 1;
while (i <= 9) {
axios.get(`http://api.tvmaze.com/shows/${i}`)
.then(response => console.log(response))
.then(response => this.setState({
title:response.data.data.name[i],
}))
.catch(error => console.log(error));
i++;
}
};
componentDidMount() {
this.getData();
console.log(this.state.title);
}
If your goal is to render your JSX after you're done fetching information, then I'd suggest creating an additional item in your state, isLoading, that you can set to true or false and render your JSX conditionally.
Based on the example you provided below, it'd look like the following:
class Shows extends React.Component {
state = {
title: [],
image: [],
rating: [],
isLoading: true
}
componentDidMount() {
this.getData()
}
getData = () => {
// I've created a URL for each request
const requestUrls = Array.from({ length: 9 })
.map((_, idx) => `http://api.tvmaze.com/shows/${idx + 1}`);
const handleResponse = (data) => {
// `data` is an array of all shows that you've requested
// extract information about each show from the payload
const shows = data.map(show => show.data)
// handle shows data however you need it
// and don't forget to set `isLoading` state to `false`
this.setState({
isLoading: false,
title: shows.map(show => show.name),
image: shows.map(show => show.url),
rating: shows.map(show => show.rating.average),
})
}
const handleError = (error) => {
// handle errors appropriately
// and don't forget to set `isLoading` to `false`
this.setState({
isLoading: false
})
}
// use `Promise.all()` to trigger all API requests
// and resolve when all requests are completed
Promise.all(
requestUrls.map(url => axios.get(url))
)
.then(handleResponse)
.catch(handleError)
}
render() {
const { isLoading, title, image, rating } = this.state
// prevent showing your `JSX` unless data has been fetched
// ideally, show a loading spinner or something that will
// tell users that things are happening;
// returning `null` won't render anything at all
if (isLoading) {
return null
}
return (
<div>...</div>
)
}
}
This way, with Promise.all, it's a lot easier to reason about all these calls that you're making.
Other than that, using componentDidMount to fetch data from an API is the right place to do it, but I'd stay away from the while loop and use Promise.all for all your requests and map to create an array of promises (requests) that can be passed to Promise.all and handled all at once.
Working example:
CodeSandbox
The way in which you are setting state will result in the last data from api to be saved in state and it will render only last call
Do it like this
getData = () => {
let i = 1;
while (i <= 9) {
axios.get(`http://api.tvmaze.com/shows/${i}`)
.then(response =>{
let prevState=this.state.title
prevState.push(response.data.data.name[i])
this.setState({
title:prevState,
})})
.catch(error => console.log(error));
i++;
}
};
I'm pretty new in React-Redux. Was working on an application. The thing is that I faced some issues with asynchronous execution of Redux actionCreator, may be.
Below is my component. Say, I want to call an actionCreator from componentDidMount() or from an onclick event listener.
class Dashboard extends PureComponent {
componentDidMount() {
this.props.getProductsAndPackages();
let something = [];
something = this.props.products;
}
....................................
}
Or , the function this.props.getProductsAndPackages(); can be an onClick event handler that does the same thing, context is the same. I'll ask my question after first explaining my code.
At the lower side of my Dashboard container:
Dashboard.propTypes = {
getProductsAndPackages: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
.......................
};
const mapStateToProps = (state) => {
return {
.....................
products: state.products.products,
...................
};
};
const mapDispatchToProps = (dispatch) => {
return {
getProductsAndPackages: () => dispatch(getProductsAndPackagesActionCreator()),
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Dashboard));
My actionCreator goes like:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
const local_token = localStorage.getItem('_token');
const fullToken = 'Bearer '.concat(local_token);
axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
});
} else {
axios.get(url)
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
console.log(error);
dispatch(productsIsLoading(false));
dispatch(productsErrors(error.message));
});
}
};
};
Now, I want my getProductsAndPackagesActionCreator() to return a Promise or anything that would allow my something variable to get the actual data returned from the server. Right now, by the time I'm getting actual data, the line something=this.props.products has already been executed and I get back the initialValue that was set for products.
I know, whenever I'll receive the populated products, component will re-render, but that does not help my decision making.
I'm using redux-thunk, by the way.
What should I do now ? Sorry for such a long post.
Actually I wanted getProductsAndPackagesActionCreator() to return a promise, which was pretty straightforward, to be honest. I figured out that if you just return the axios.get() or axios.post(), it will return a promise. So, the modified code looked like below:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
return axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
............
............
})
.catch(error => {
});
} else {
return axios.get(url)
.then(response => {
...........
...........
})
.catch(error => {
console.log(error);
});
}
};
};
And then, I could do something like below in componentDidMount() or on any onClick event:
this.props.getProductsAndPackages().then(() => {
this.setState({
...this.state,
clicked_product: this.props.product_by_id
}, () => {
//do other stuffs
});
});
Feel free to let me know if there's any issue.
I think you are close to getting what you want. First of all, you should understand that redux actions and react actions like setState are asynchronous, so you have to apply your logic keeping this in mind. I'm going to explain what i think in some points:
You have called the action creator in the correct place componentDidMount, also you can call this action in any onClick if you want.
As soon as you dispatch the action you are changing your redux state setting loading true I suppose. So now you can access this property in your render function, so you can render a Loader until your api call finishes.
When your ajax function finishes, with an error or not, I suppose you are setting loading to false and updating your products data, so you can render now your loaded products in your dashboard.
Are you sure that you have to compare your empty products array with the received data? Maybe you can check in your render function if (!this.props.products.length) return null, when you load your page you will see a loader function and later your dashboard with the products.
If you really need to compare previous products with received products componentDidUpdate is your method. In this method, you can access your previous props and compare with actual props, be careful comparing arrays, remember [] === [] is false. Maybe you can compare the length, something like
componentDidUpdate(prevProps){
if(prevProps.products.length !=== this.props.products.lenth){
doSomething()
}
}
Just to say that componentDidUpdate is executed after render, so be careful with your code to no-execute extra renderings.
Hope it helps, if you dont understand anyting just tell me :)
I'm fetching the data from the following call which returns an object:
function getPersonList() {
const api = 'myapistring';
axios.get(api).then(res => {
console.log(res);
}).catch(err => console.log(err));
}
1 - However when I hit my componentDidMount. The promise is breaking and I don't know why.
2- Also since the response is an object, am I doing something wrong by setting the initial state to an empty [ ]?
-I'm not sure what's the syntax to set it as an object.
const App = React.createClass({
getInitialState() {
return {
personList: [],
visiblePersonList: []
};
},
componentDidMount() {
console.log(getPersonList(response));
getPersonList().then((data) =>
this.setState({
data,
visiblePersonList: data
}));
//return getPersonList;
},
.....
Thank you everyone!
You aren't returning anything out of getPersonList
function getPersonList() {
const api = 'myapistring';
return axios.get(api).then(res => { // FIX THIS LINE WITH return
console.log(res);
}).catch(err => console.log(err));
}
Here is a typical container component that is working perfectly well:
const API = 'https://randomuser.me/api/?results=5';
class App extends Component {
constructor(props) {
super(props);
this.state = { profiles: [] };
}
componentDidMount() {
this.fetchProfiles();
}
fetchProfiles() {
let url = API;
fetch(url)
.then( (res) => res.json() )
.then( (data) => {
let results = data.results;
this.setState({
profiles: results
});
})
.catch( (error) => console.log('Oops! . There Is A Problem', error) );
}
render() {
// rendering child component here
}
}
export default App;
What I am trying to do now is move the fetchProfiles function into a separate api component.
So I make a profiles.js file in an api folder in my project:
const API = 'https://randomuser.me/api/?results=5';
export function fetchProfiles() {
let url = API;
fetch(url)
.then( (res) => res.json() );
}
And now my main component imports it and uses it like so:
import { fetchProfiles } from '../api/profiles.js';
const API = 'https://randomuser.me/api/?results=5';
class App extends Component {
constructor(props) {
super(props);
this.state = { profiles: [] };
}
componentDidMount() {
fetchProfiles.then((data) => {
let results = data.results;
this.setState({
profiles: results
});
});
}
// render call etc
But when I run the call in componentDidMount like this, I get this error: Uncaught TypeError: _profiles.fetchProfiles.then is not a function. I am trying to chain with then because the fetch api returns res.json() as a promise.
I tried wrapping fetchProfiles in a outer function, in a new promise too! But nothing works!! What am I doing wrong here? Please help with this refactoring.
You need to return fetch(url) itself, so you'll return a promise and then you can use then method:
const API = 'https://randomuser.me/api/?results=5';
export function fetchProfiles() {
let url = API;
// return the promise itself
return fetch(url).then( (res) => res.json() );
}
The way I fixed this was to return fetch(url) itself:
const API = 'https://randomuser.me/api/?results=5';
export function fetchProfiles() {
let url = API;
return fetch(url)
.then( (response) => response.json() );
}
Then in the container component:
componentDidMount() {
fetchProfiles()
.then( (data) => {
let results = data.results;
this.setState({
profiles: results
});
});
}
This now works!!
I once made a similar app that i separated the API calls from the context (i used context API btw) instead of returning the fetch function and handle the async calls on the context, i handled everything on the function declared on the API and on the context only recieved the result, this seems to be a little bit more complicated but it simplifies the data handling on the context and all the data fetching login on the other side, i also used axios for the API call but it's easily swapped with the fetch one (axios handles better errors like handling of timeouts or service not available)
on profiles.js:
import axios from 'axios';
const API = 'https://randomuser.me/api/?results=5';
export const fetchProfiles = async () => {
let url = API;
try{
const response = await axios.get(url)
const data = response.data
return data
}catch(error) {
console.log('failed to fetch Profiles')
throw Error(error)
}
}
on the context:
import { fetchProfiles} from "../API/profiles";
const getProfiles = async () =>{
const result = await fetchProfiles();
this.setState({
profiles: result
});
}