React fetch doesn't display value from nested object - reactjs

I have a problem with getting value from nestle object. I want to get value name from studySets (this is a nested object in object user). The program knows that it is an object but it can't display value (show two dots because, in the database, it is two user objects). I have added a screenshot with the response. Please tell me where is the problem.
WordSet.js
export class WordSet extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
};
}
componentDidMount() {
fetch(URL)
.then((response) => response.json())
.then((json) => {
this.setState({ users: json });
});
}
render() {
const { users } = this.state;
return (
<div>
<ul>
{users.map((user) => (
<li key={user.id}>{user.studySets.name}</li>
))}
</ul>
</div>
);
}
}
export default WordSet;

ok basically you are treating the studySets as object which is rather an array. so either you have to loop over the studySets array as well or if you always want the data of the first user then do something like this as I mentioned in the comment:
{user.studySets[0].name}
in the
<li>

Related

React .map only returning first item

I've just started my React journey recently. I am currently trying to render properties of an array of objects which is returned from my controller.
The json:
[
{
"reportID":4,
"reportDescription":"Commission Bonus Register",
"reportNotes":"",
"reportName":"CommissionBonusRegister"
},
{
"reportID":5,
"reportDescription":"Reset Government ID",
"reportNotes":"",
"reportName":"ResetGovtID"
},
{
"reportID":6,
"reportDescription":"Distributor Chase Up Report",
"reportNotes":"",
"reportName":"DistributorChaseUpReport"
},
{
"reportID":7,
"reportDescription":"Vietnam Distributor Export",
"reportNotes":"",
"reportName":"VietnamDistributorExport"
},
{
"reportID":8,
"reportDescription":"Vietnam Order Export",
"reportNotes":"",
"reportName":"VietnamOrderExport"
},
{
"reportID":9,
"reportDescription":"Distributor List by status and period",
"reportNotes":"",
"reportName":"DistributorsList"
}
]
React component code:
constructor(props) {
super(props);
this.state = {
linkscontent: [],
loading: true,
refresh: true
}
this.populateReportsLinks = this.populateReportsLinks.bind(this);
}
async componentDidMount() {
await this.populateReportsLinks();
}
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
:
this.state.linkscontent.map(([reports], index) => {
return <li key={reports.reportID}>{reports.reportDescription}</li>
});
return (
<div>
<h1 id="tabelLabel" >Reports</h1>
<ul>
{contents}
</ul>
</div>
);
}
async populateReportsLinks() {
const response = await fetch('reports')
.then(res => res.json())
.then(data =>
this.setState({ linkscontent: [data], error: data.error || null, loading: false, refresh: !this.state.refresh }));
return response;
}
After two days of frustration I have finally managed to get the first item to display, but only the first item. Ive read so many articles and forum solutions that seem to indicate this should work. Can anyone help me figure out what is wrong here?
Remove the [data] to just this.setState({ linkcontent: data, ...restOfUpdates }) after you have fetched your data.
While mapping don't destructure with [reports] just use the reports.
async componentDidMount() {
await this.populateReportsLinks();
}
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: this.state.linkscontent.map((reports, index) => {
return <li key={reports.reportID}>{reports.reportDescription}</li>
});
return (
<div>
<h1 id="tabelLabel" >Reports</h1>
<ul>
{contents}
</ul>
</div>
);
}
async populateReportsLinks() {
const response = await fetch('reports')
.then(res => res.json())
.then(data =>
this.setState({ linkscontent: data, error: data.error || null, loading: false, refresh: !this.state.refresh }));
return response;
}
You have a few problems with your logic. Let's look at them one by one.
When you set up state with your data
this.setState({ linkscontent: [data],...}
So when you do the above its basically makes linkscontent an array but only of one length. That means on its first index you have an array of your data.
When you run map like this
this.state.linkscontent.map(([reports], index)
That means you want to iterate through each index of linkscontent but since you have only one index in linkscontent you will get only one item printed.
How to fix.
There are a few ways to fix it. You can try saving data into the state as per below code. This will make linkscontent an array with the data source.
this.setState({ linkscontent: [...data],...}
Or
this.setState({ linkscontent: data,...}
then run map like this
this.state.linkscontent.map((report, index) => <li key={report.reportID}>{report.reportDescription}</li>)
With your current version of setting linkscontent of one length, you can run your map like this as well
this.state.linkscontent.length && this.state.linkscontent[0].map((report, index) => ...)
Yeah it comes down to your state not the mapping function. spread the results into an array. this also happens quite often when you incorrectly mutate or update the state.

How Can I Filter the data using Search Field

I Have an Search Field Input , Through that Input I need to filter the data.
So, How Can I Filter the data using Search Field..
Can Anyone help me in this..
class App extends Component {
constructor() {
super ();
this.state = {
monsters : [],
searchFeild :'',
};
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => this.setState({monsters : users}))
}
inputData =(event)=>{
console.log(event.target.value);
this.setState({searchFeild : event.target.value});
}
render() {
return (
<div className="App">
<input type ="text" placeholder ="Typeo" onChange={this.inputData}/>
<CardList monsters ={this.state.monsters}/>
</div>
);
}
}
export default App;
use the filter function on the monsters
monsters.filter(m => !searchfield || m.name.contains(searchfield))
also your input is missing value. Check here for more info
https://reactjs.org/docs/forms.html#controlled-components
The complexity of your search is all up to you and how your monsters data-set looks. Let's say your array of monsters looks something like this:
monsters = [{name: "Pikachu"}, {name: "Meowth"}, {name: "Charmander"}]
You could do a very simple search to check whether the string you enter in the input is included in the name of the monster. It would be as simple as using array.protoype.filter() and string.prototype.includes() or string.prototype.contains()
Enhance your change-handler to:
inputData = (event) => {
const { value } = event.target
const { monsters } = this.state
const filteredMonsters = monsters.filter((monster) => {
return monster.name.includes(value)
})
this.setState({
monsters: filteredMonsters
})
}
But again, this is a very primitive search, you could enhance it by accounting for letter-casing, spaces, Regex and etc.

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

React trying to use .map to populate data from multiple APIs into the same mapped container

I'm trying to practice integrating data from separate APIs, but I'm having trouble conceptualizing what the right way is to do so.
Basically, I have two sample APIs I'm fetching from:
1. const API = 'https://hn.algolia.com/api/v1/search?query='; (this.state.hits)
const DEFAULT_QUERY = 'redux';
and
2. https://randomuser.me/api/?results=50 (this.state.randomPeople)
I pull them into their own arrays in state via a method called in componentDid Mount.
this.state = {
hits: [],
randomPeople: [],
};
Ideally, I'd like to map over both of them and have data available from each .map result to populate in a single container, something like:
<div>
<img src={random.thumbnailPic}/>
<h3>{random.name.first}</h3>
<h3>{random.name.last}</h3>
<p>{hit.title}</p>
</div>
Just not sure how to approach this the best way. I have only mapped over one data source when populating the results to a container. Should I combine the two arrays and store them together in state? I looked at Lodash, would that work here? Or is there a better way to accomplish this that I just haven't found?
Right now I just have them right on top of another in render() :
{hits.map(hit => (
<div key={hit.objectID}>
<a href={hit.url}>{hit.title}</a>
</div>
))}
{randomPeople.map(rando => (
<div key={random.email}>
<img src={random.picture.medium} />
<h3>Author: {random.name.first} {random.name.last}</h3>
</div>
))}
And here are my methods:
fetchHits = () => {
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ... ');
}
})
.then(data => this.setState({ hits: data.hits, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
};
fetchRandomPeople = () => {
fetch('https://randomuser.me/api/?results=50')
.then(results => results.json())
.then(data => this.setState({ randomPeople: data.results }));
};
componentDidMount() {
this.fetchRandomPeople();
this.fetchHits();
this.setState({ isLoading: true });
}
Thanks!
If you're assuming that hits and randompeople are going to be the same length, or if you can somehow align the two arrays, you could add the index parameter to your .map() function:
{randomPeople.map((rando, i) => (
<div key={rando.email}>
<img src={rando.thumbnailPic}/>
<h3>{rando.name.first}</h3>
<h3>{rando.name.last}</h3>
<p>{hits[i].title}</p>
</div>
)}

React: Google Places API/ Places Details

I have the following code which retrieves Google Places Reviews based on Google Places API. I have incorporated the logic to work as a React life cycle component. Currently, I am unable to setState and correctly bind the object. I could use some help understanding where my logic is failing.
export default class Reviews extends React.Component{
constructor(props){
super(props);
this.state = {
places: []
}
}
componentDidMount(){
let map = new google.maps.Map(document.getElementById("map"), {
center: {lat:40.7575285, lng: -73.9884469}
});
let service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'ChIJAUKRDWz2wokRxngAavG2TD8'
}, function(place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log(place.reviews);
// Intended behavior is to set this.setState({places.place.reviews})
}
})
}
render(){
const { places } = this.state;
return(
<div>
<p>
{
places.map((place) => {
return <p>{place.author_name}{place.rating}{place.text}</p>
})
}
</p>
</div>
)
}
}
You can't use this that way in a callback. When the function is called the this in, this.setState({places.place.reviews}) doesn't point to your object. One solution is to use => function notation which will bind this lexically.
service.getDetails({
placeId: 'ChIJAUKRDWz2wokRxngAavG2TD8'
}, (place, status) => {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log(place.reviews);
this.setState({places: place.reviews})
}
})
}
Alternatively you can make a new reference to this and us it in the function. Something like
var that = this
...
that({places.place.reviews})
The first option is nicer, but requires an environment where you can use ES6. Since your using let you probably are okay.
With some tweaking -- I got the code to work! Thank you.
render(){
const { places } = this.state;
return(
<div>
<p>
{
places.map((place) => {
if(place.rating >= 4){
return <p key={place.author_name}>{place.author_name}{place.rating}{place.text}</p>
}
})
}
</p>
</div>
)
}

Resources