Trying to render a list of movies from a random API and eventually filter them.
componentDidMount() {
var myRequest = new Request(website);
let movies = [];
fetch(myRequest)
.then(function(response) { return response.json(); })
.then(function(data) {
data.forEach(movie =>{
movies.push(movie.title);
})
});
this.setState({movies: movies});
}
render() {
console.log(this.state.movies);
console.log(this.state.movies.length);
return (
<h1>Movie List</h1>
)
}
If I render this I can only print my state and not access what is inside.
How would I create a list of LIs and render a UL?
Thanks
A few things. fetch is asynchronous, so you're essentially just going to be setting movies to an empty array as this is written. If data is an array of movies, you can just set that directly in your state rather than copying it to a new array first. Finally, using an arrow function for the final callback in the promise will allow you to use this.setState without having to explicitly bind the function.
Finally, you can use JSX curly brace syntax to map over the movies in your state object, and render them as items in a list.
class MyComponent extends React.Component {
constructor() {
super()
this.state = { movies: [] }
}
componentDidMount() {
var myRequest = new Request(website);
let movies = [];
fetch(myRequest)
.then(response => response.json())
.then(data => {
this.setState({ movies: data })
})
}
render() {
return (
<div>
<h1>Movie List</h1>
<ul>
{this.state.movies.map(movie => {
return <li key={`movie-${movie.id}`}>{movie.name}</li>
})}
</ul>
</div>
)
}
}
First of all, since fetch is asynchronous, you will most likely call setState before the fetch call has completed, and you obviously don't want to do that.
Second, if the data returned is an array of movies, do not iterate over it, just assign the array altogether to the movies state variable.
So, in short, this is what your componentDidMount method should look like :
componentDidMount() {
var myRequest = new Request(website);
fetch(myRequest)
.then(res => res.json())
.then(movies =>
this.setState({ movies }) // little bit of ES6 assignment magic
);
}
Then in your render function you can do something like:
render() {
const { movies } = this.state.movies;
return (
<ul>
{ movies.length && movies.map(m => <li key={m.title}>{m.title}</li>) }
</ul>
);
}
Gotten your movies in state you can do the next to render the list of movies:
render() {
return (
<h1>Movie List</h1>
<ul>
{
this.state.movies.map((movie) =>
<li key={movie}>movie</li>
)
}
</ul>
);
}
Related
Learning react
Trying to loop through an object from an API call that returns a json object and display it but struggling to implement it
This is the component that should render it
export default class ProfilePage extends Component {
constructor() {
super();
this.state = { data: '' };
}
mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
})
}
async componentDidMount() {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const json = await response.json();
// console.log(json)
this.setState({ data: json });
}
render() {
const data = this.state.data
console.log(data)
return (
<div className="row">
{Object.values(data).map(data => {
<div key={key}>
{data[key]}
</div>
})
}
Woerkkk please
</div>
);
}
}
All I'm getting is a blank screen.
in the console i get the error 'key' is not defined no-undef
You are missing a return statement in your map for your render method.
Edit: Key is not returned from Object.values
Either reconfigure with a return statement like so:
{Object.keys(data).map(key => {
return (<div key={key}>
{data[key]}
</div>);
})
Or alternatively you can implicitly return from arrow function using brackets
{Object.keys(data).map(key => (
<div key={key}>
{data[key]}
</div>)
))
Using Object.values(myObj) you can get all object values as a array. So, with this array, you can iterate over the array and show your items, like this:
{Object.values(myObj).map(value => <p>{value}</p>)}
Don't forget use key prop when iterating.
You can use useState and useEffect to fetch the object data
const App = () => {
const [objData, setObjData] = useState({});
const [objItems, setObjItems] = useState([]);
const fetchObj = async () => {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const data = await response.json();
setObjData(data);
setObjItems(data.items);
}
useEffect(() => {
fetchObj()
},[]);
return(
<div>
<h1> Order Id :{objData.order_id}</h1>
// or any other objData keys
<h1>Items : </h1>
<ul>
{
objItems.map((i, idx) => {
return(
<li key={idx}>Name : {i.name} , Category: {i.category}, Price: {i.price}, Currency: {i.currency}</li>
)
})
}
</ul>
</div>
)
}
export default App;
I am trying to have a search bar which updates the set of json entries depending upon query. Here's the code that displays the video list (<Videos videos={this.state.data}/>). When the page loads, I want to call <Videos videos={this.state.data}/>, but after this query from search bar should update this list. My search functionality is not working for some reason.
class App extends Component {
state = {
query: "",
data: [],
filteredData: []
};
handleInputChange = event => {
const query = event.target.value;
this.setState(prevState => {
const filteredData = prevState.data.filter(element => {
return element.title.toLowerCase().includes(query.toLowerCase());
});
return {
query,
filteredData
};
});
};
getData = () => {
fetch('http://localhost:3000/api/videos')
.then(response => response.json())
.then(data => {
const { query } = this.state;
const filteredData = data.filter(element => {
return element.title.toLowerCase().includes(query.toLowerCase());
});
this.setState({
data,
filteredData
});
});
};
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input
placeholder="Search for..."
value={this.state.query}
onChange={this.handleInputChange}
/>
</form>
<Videos videos={this.state.data}/>
<div>{this.state.filteredData.map(i => <p>{i.name}</p>)}</div>
</div>
);
}
}
I am new to react, any pointers will be appreciated.
You are creating a new object without the data property and setting that object as the state whenever you are calling this.setState. So the data property is getting deleted.
On the handleInputChange method do this:
return {
...this.state,
query,
filteredData
};
And on the getData method do this:
this.setState({
...this.state,
data,
filteredData
});
You are using this.state.data in the Videos component which you got from the server. You should use this.state.filteredData to show entries depend on query:
<Videos videos={this.state.filteredData}/>
You need to bind the handleInputChange to this.
Look at this for more on this.
I have an array data in my state, that contains a collection of objects, however when I try to access and render some of the information stored in the objects, none of the expected text appears on the page.
My current code for this is in my render function as follows:
<ul>
{this.state.data.map(match =>
<Link to={'teams/'+ team.homeId} key={match.id}><li>{match.homeTeam}</li></Link>
<ul>
When I check the elements in my console, I see undefined where I'm expecting my data.
The information is retrieved inside an await axios.get() request inside my async componentDidMount() function, which leads me to believe that the data may not be displaying correctly as it has not yet been retrieved when the render occurs, however I'm not sure.
I've previously tried to define let match = this.state.data || {} at the beginning of my render, so that my render would reload once the setState had taken place in my componentDidMount, however this did not work. Any directions would be appreciated.
EDIT as per MonteCristo request;
class Welcome extends Component {
constructor(props) {
super(props)
this.state = {
data: []
}
}
async componentDidMount() {
let res = await axios.get(`https://soccer.sportmonks.com/api/v2.0/livescores?api_token=${API}`)
let matchInfo = new Array(res.data.data.length).fill().map(_ => ({}))
res.data.data.forEach((id, i) => Object.assign(matchInfo[i], {id: id.id, homeScore: id.scores.localteam_score}, {awayScore: id.scores.visitorteam_score}))
res.data.data.forEach((id, i) => {
axios.get(`https://soccer.sportmonks.com/api/v2.0/teams/${id.localteam_id}?api_token=${API}`)
.then(res1 => {
Object.assign(matchInfo[i], {homeId: res1.data.data.id, homeTeam: res1.data.data.name})
})
})
res.data.data.forEach((id, i) => {
axios.get(`https://soccer.sportmonks.com/api/v2.0/teams/${id.visitorteam_id}?api_token=${API}`)
.then(res2 => {
Object.assign(matchInfo[i], {awayId: res2.data.data.id, awayTeam: res2.data.data.name})
})
})
res.data.data.forEach((id, i) => {axios.get(`https://soccer.sportmonks.com/api/v2.0/leagues/${id.league_id}?api_token=${API}`)
.then(res3 => {
Object.assign(matchInfo[i], {leagueName: res3.data.data.name})
})
})
this.setState({
data: [...this.state.data, ...matchInfo]
})
}
render() {
return (
<div>
<p className="text-xl font-semibold">Live Matches</p>
<div>
<ul>
{this.state.data.map(match =>
<Link to={'teams/'+ match.homeId} key={match.id}><li>{match.homeTeam}</li></Link>)}
</ul>
</div>
</div>
);
}
}
export default Welcome;
You need to actually wait for the requests to complete.
You're doing a this.setState({ data: [...this.state.data, ...matchInfo] }) at the bottom, but you are mutating the matchInfo objects asynchronously, so setState is occurring before all those axios requests complete. Mutating state will not cause re-renders, only setting it via setState.
If you await all your axios calls instead and build your object before calling setState, you'll get what you are looking for:
async componentDidMount() {
const soccerApi = axios.create({
baseURL: 'https://soccer.sportmonks.com/api/v2.0',
params: { api_token: API }
});
const res = await soccerApi.get(`/livescores`);
const matchInfo = await Promise.all(res.data.data.map(async row => {
const [homeTeamInfo, awayTeamInfo, leagueInfo] = await Promise.all([
soccerApi.get(`/teams/${row.localteam_id}`),
soccerApi.get(`/teams/${row.visitorteam_id}`),
soccerApi.get(`/leagues/${row.league_id}`)
]);
return {
id: row.id,
homeScore: row.scores.localteam_score,
awayScore: row.scores.visitorteam_score,
homeId: homeTeamInfo.data.data.id,
homeTeam: homeTeamInfo.data.data.name,
awayId: awayTeamInfo.data.data.id,
awayTeam: awayTeamInfo.data.data.name,
leagueName: leagueInfo.data.data.name
};
}));
this.setState({
data: [...this.state.data, ...matchInfo]
});
}
You need to check that data was retrieved successfully like this:
<ul>
{this.state.data && this.state.data.map(match => {
return (<Link to={'teams/'+ team.homeId} key={match.id}>{match.homeTeam}</Link>)
}
<ul>
Main issue is your axios calls in forEach. you're not waiting for them. you are calling them within the forEach instead of waiting.
so you'd need to do something like this
const allPromises = res.data.data.map((id, i) => { return axios.get() }
// then
const allResponseValues = await axios.all(allPromises).catch(console.error)
// do something with allResponses and update matchInfo
// finally
this.setState({...})
also update your render function to below for readability and clarity
{this.state.data.map(match => (
<Link to={"teams/" + match.homeId} key={match.id}>
<li>{match.homeTeam}</li>
</Link>
))}
I need to render a component after data is fetched. If try to load data instantly, component gets rendered but no data is show.
class App extends React.Component {
//typical construct
getGames = () => {
fetch(Url, {})
.then(data => data.json())
.then(data => {
this.setState({ links: data });
})
.catch(e => console.log(e));
};
componentDidMount() {
this.getGames();
}
render() {
return (
<div className="App">
<Game gameId={this.state.links[0].id} /> //need to render this part
after data is received.
</div>
);
}
}
You could keep an additional piece of state called e.g. isLoading, and render null until your network request has finished.
Example
class App extends React.Component {
state = { links: [], isLoading: true };
getGames = () => {
fetch(Url, {})
.then(data => data.json())
.then(data => {
this.setState({ links: data, isLoading: false });
})
.catch(e => console.log(e));
};
componentDidMount() {
this.getGames();
}
render() {
const { links, isLoading } = this.state;
if (isLoading) {
return null;
}
return (
<div className="App">
<Game gameId={links[0].id} />
</div>
);
}
}
You can do like this using short circuit.
{
this.state.links && <Game gameId={this.state.links[0].id} />
}
Can we use the pattern of "Render-as-you-fetch" to solve the problem.
Using a flag to check whether loading is complete doesn't look like a clean solution..
I'm trying to learn React and I'm a beginner when it comes to Javascript. Right now I'm working on an app that is fetching data from Flickr's API. The problem is that when I try to use the map method on the props in the Main.js component I get an error saying "Uncaught TypeError: this.props.photos.map is not a function". After searching here on Stackoverflow I think the problem is that this.props are javascript objects and not an array. The problem is that I can't figure out how to make it an array. Can anyone explain what I'm doing wrong?
My code:
class App extends Component {
constructor() {
super();
this.state = {
}
}
componentDidMount() {
let apiKey = 'xxxxxxxxxxxxxxxxxx';
let searchKeyword = 'nature';
let url = `https://api.flickr.com/services/
rest/?api_key=${apiKey}&method=flickr.photos.
search&format=json&nojsoncallback=1&&per_page=50
&page=1&text=${searchKeyword}`;
fetch(url)
.then(response => response.json())
.then(data => data.photos.photo.map((x) => {
this.setState({
farm: x.farm,
id: x.id,
secret: x.secret,
server: x.server})
// console.log(this.state)
}))
}
render() {
return (
<div className="App">
<Header />
<Main img={this.state.photos} />
<Navigation />
</div>
);
}
}
export default class Main extends Component {
render() {
return(
<main className="main">
{console.log(this.props.photos)}
</main>
)
}
}
Edit:
Why is this.props.img undefined first?
Screen shot from console.log(this.props.img)
fetch(url)
.then(response => response.json())
.then(data => data.photos.photo.map((x) => {
this.setState({
farm: x.farm,
id: x.id,
secret: x.secret,
server: x.server})
}))
What is happening is that your map function in your promise is resetting the component's state for every photo that is returned. So your state will always be the last object in your list of returned photos.
Here is a more simplified example of what I am referring to
const testArray = [1,2,3,4];
let currentState;
testArray.map((value) => currentState = value)
console.log(currentState);
What you want to do is this
const testArray = [1,2,3,4];
let currentState;
//Notice we are using the return value of the map function itself.
currentState = testArray.map((value) => value)
console.log(currentState);
For what you are trying to accomplish, you want your state to be the result of the map function (since that returns an array of your results from the map). Something like this:
fetch(url)
.then(response => response.json())
.then(data =>
this.setState({
photos:
data.photos.photo.map((x) => ({
farm: x.farm,
id: x.id,
secret: x.secret,
server: x.server
}))
})
)
This error might also happen if you try to provide something else other than the array that .map() is expecting, even if you declare the variable type properly. A hook-based example:
const [myTwinkies, setMyTwinkies] = useState<Twinkies[]>([]);
useEffect(() => {
// add a twinky if none are left in 7eleven
// setMyTwinkies(twinkiesAt711 ?? {}); // ---> CAUSES ".map is not a function"
setMyTwinkies(twinkiesAt711 ?? [{}]);
}, [twinkiesAt711, setMyTwinkies]);
return (<ul>
{myTwinkies.map((twinky, i)=> (
<li key={i}>Twinky {i}: {twinky?.expiryDate}</li>
))}
</ul>)
Just check the length of the array before going for the map. If the len is more than 0 then go for it, otherwise, ignore it.
data.photos.photo.map.length>0 && data.photos.photo.map(........)