conditional rendering at empty response of API in react js - reactjs

I am building a little react js application where users can select a few filters and get response accordingly. For some values selected by users, nothing is found from database and I want to show "nothing found" message.
I tried using if & else conditional operators which is not producing results. Below is the code.
.then(res => {
if(!res.data.length){
return(
<div>
<h1>nothing found.</h1>
</div>
)
}
else{
this.setState({ data: res.data,urlList:[] })
console.log(this.state)
}
})
Now If I do this
.then(res => {
if(!res.data.length){
console.log('nothing found')
}
else{
this.setState({ data: res.data,urlList:[] })
console.log(this.state)
}
})
I am getting a response on console. What I am doing wrong ?

declare a new state variable like
constructor(props){
super(props);
this.state = {
noData: ''
}
}
And here
.then(res => {
if(!res.data.length){
this.setState({noData: 'nothing found'});
console.log('nothing found')
}
else{
this.setState({ data: res.data,urlList:[], noData: '' })
console.log(this.state)
}
})
And in render just display this.state.noData
render(){
return(
<div>
<h1>{this.state.noData}</h1>
</div>
)
}

Log out the res.data object for a case when nothing is found. It may not be an empty array as your code assumes, but might contain some message to the effect that nothing on the database matches your query. You need to check for this rather than simply !res.data.

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 do make a loading progress when I fetching an API in login function?

I am stuck on certain project for showing the loading when I try to fetch an API for login. I use
Consider the code below:
login(){
const { history } = this.props;
PostData('api/users/login', this.state)
.then ((result) => {
let responseJSON = result;
this.setState({
loaded: true
})
if(this.state.loaded === true){
if(responseJSON.success === true){
localStorage.setItem('loginEmail', this.state.loginEmail);
localStorage.setItem('id', responseJSON.user.id);
history.push('/Home') // when successfully login, it will go to Home Page
}else if(responseJSON.success === false){
alert("Wrong User Credential")
}}else if(this.state.loaded === false){
return(
<LinearProgress /> // for loading
)
}
}).catch((error) => {
console.log(error)
})
}
and here is my contructor for this.state
constructor (props){
super(props);
this.state ={
loginEmail: '',
loginPassword: '',
error: {},
loaded: false,
history: PropTypes.object.isRequired
}
this.login = this.login.bind(this);
this.onChange = this.onChange.bind(this);
}
is this the way I use for loading in my login function is wrong? Why I cannot show the linear progress when the loaded is false?
Update
Here is my code from codesandbox
Remove else if case from login function. This one.
else if (this.state.loaded === false) {
return (
<LinearProgress /> // for loading
);
}
and In render below your login input field add a ternary condition that checks state and if state is false renderes <LinearProgress /> and when state is made true by login that will move to next page.
<input
type="submit"
value="Login"
className="logbtn"
onClick={this.login}
/>
{(this.state.loaded)?null: <LinearProgress />}
Based on your sandbox, there are a couple of things you need to add/change
Rename loaded to isLoading parameter to your state so it will be easier to understand it's usability
this.state = {
loginEmail: "",
loginPassword: "",
error: {},
redirect: false,
isLoading: false,
history: PropTypes.object.isRequired
};
Trigger the state change to true when you fire the call, there are a couple of ways to do it but the simplest is to change the isLoading directly in the login function
Add a finally promise handler so after the call to change the loading state to false in either case, success or fail.
login() {
const { history } = this.props;
this.setState({isLoading:true});
PostData("api/users/login", this.state)
.then(result => {
let responseJSON = result;
if (responseJSON.success === true) {
localStorage.setItem("loginEmail", this.state.loginEmail);
localStorage.setItem("id", responseJSON.user.id);
history.push("/Home"); // when successfully login, it will go to Home Page
} else if (responseJSON.success === false) {
alert("Wrong User Credential");
}
})
.catch(error => {
console.log(error);
})
.finally(() => this.setState({ isLoading : false }));
}
The last thing is to add your <LinearProgress /> element in the page and set the conditional for it. In your case isLoading to be true. You can place the element anywhere inside the render by adding the following line
{this.state.isLoading && <LinearProgress />}

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

ReactJS+FireStore Data mapping issue

Im writing a small program to fetch the categories from the Firestore DB and show in webpage as a list.
My code look like this:
class Category extends Component {
constructor() {
super();
this.state = {'Categories': []}
}
render() {
let categoryList = null;
if (Array.isArray(this.state.Categories)) {
console.log(this.state.Categories);
categoryList = this.state.Categories.map((category) => {
return <li>{category.name}</li>
});
}
return(
<ul>{categoryList}</ul>
);
}
componentWillMount() {
// fetch the data from the Google FireStore for the Category Collection
var CategoryCollection = fire.collection('Category');
let categories = [];
CategoryCollection.get().then((snapshot)=> {
snapshot.forEach ((doc) => {
categories.push(doc.data());
});
}).catch((error) => {
console.log("Error in getting the data")
});
this.setState({'Categories': categories});
}
}
Im able to fetch the data and even populate this.state.Categories, however the map function is not getting executed.
The console.log statement produce an array of values butthe map function in render is not getting executed. Any thoughts?
Console.log output:
You have an error in handling data retrieval. In the last line categories is still empty, so it triggers setState with an empty data set. Should be something lie that
componentWillMount() {
fire.collection('Category').get()
.then(snapshot => {
const categories = snapshot.map(doc => doc.data());
// sorry, but js object should be pascal cased almost always
this.setState({ categories });
})
.catch(error => {
console.log("Error in getting the data")
});
}
Only return the data if the data exists. The simplest way to do this is to replace <ul>{categoryList}</ul> with <ul>{this.state.categories && categoryList}</ul>
I could make it work with a small change (moved this.setState to be inside the callback). Honestly, I still don't understand the difference.
P.S: I come from PHP development and this is my first step into ReactJS.
componentWillMount() {
// fetch the data from the Google FireStore for the Category Collection
var categoryCollection = fire.collection('Category');
let categories = [];
categoryCollection.get().then((snapshot)=> {
snapshot.forEach ((doc) => {
categories.push(doc.data());
});
if (categories.length > 0) {
this.setState({'Categories': categories});
}
}).catch((error) => {
console.log("Error in getting the data");
});
// this.setState({'Categories': categories});
}

React - Json Schema Form dropdowns won't load initally unless I use SetTimeout function

This has been driving me and my team crazy. Here is the relevant code.
In the component's CDM we have:
componentDidMount() {
this.getContextID();
this.getConsumerID();
this.getEnvType();
//setTimeout(() => this.setState({ populatedMultiSchema: this.multiSchema }), 200);
//setTimeout(() => this.setState({ populatedMultiUISchema: this.multiUISchema }), 200);
this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });
}
so any one of the 3 methods listed will fetch the data for the dropdown. Here is an example of one (they are all basically the same).
getContextID() {
contextIDOptions = [];
console.log("CONVERT_TARGET::", this.props.fetchTarget)
return (
fetch(this.props.fetchTarget + "Configuration/ContextIDs", {
method: 'GET',
//mode: 'cors',
credentials: 'include',
}).then(response => {
if (response.status >= 400) {
this.setState({
value: 'no response - status > 400'
});
throw new Error('no response - throw');
}
return response.json()
}).then(function (json) {
for (var contextID = 0; contextID < json.List.length; contextID++) {
contextIDOptions.push(json.List[contextID]);
}
this.setState({ contextIDArray: contextIDOptions });
console.log("got contextIDs");
}.bind(this)).catch(() => {
this.setState({
value: 'no response - cb catch'
})
})
)
}
So we set the state there to 'contextIDArray'.
Then the JSON Schema form through it's multiUISchema Object has references to these widgets that help set the values for the form.
ContextIDWidget = (props) => {
return (
<div>
<input type="text" placeholder="Select one..." className="form-control" list="contextIDSelect" onChange={(event) => props.onChange(event.target.value)} />
<datalist id="contextIDSelect">
{this.state.contextIDArray.map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
This is the multiUISchema object (the part that matters for this discussion)
multiUISchema = {
file: {
'ui:widget': this.MultiFileWidget,
classNames: "uiSchema"
},
contextID: {
'ui:widget': this.ContextIDWidget,
classNames: "uiSchema"
},
}
And finally here it is in the return in the component.
return (
<div className="container" >
<Form
schema={this.state.populatedMultiSchema}
uiSchema={this.state.populatedMultiUISchema}
formData={this.state.formData}
onChange={({ formData }) => { this.setState({ formData }); this.setState({ totalFileSize: this.getMultiFileSize() }); this.checkConversionSupport() }}
onSubmit={this.handleSubmit}
>
So long story short, if Im using state object in the form, and Im doing setState on the objects Im using. Why do I always get a blank dropdown when I first load the component. Shouldn't the DOM (the dropdown in this case) get repainted with the updated data from the fetches when the state object is changed? I have console logs that show the fetched data in my inspection window so I know the data has been fetched. This is tab component. If I leave the tab or navigate to another page in my SPA and then go back to this page, then the dropdowns are all fully loaded. But I can never get it just load initially unless I set these timeouts in CDM instead of just setting state.
setTimeout(() => this.setState({ populatedMultiSchema: this.multiSchema }), 200);
setTimeout(() => this.setState({ populatedMultiUISchema: this.multiUISchema }), 200);
I know the post is long but felt I needed to include all the parts to get help with this. I can assure you we have been trying to resolve the issue for over a week. We welcome any comments. Thanks!
I am not fully familiar to your codebase. But it looks like something related about asynchronous requests. Here:
this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });
These two lines will be executed BEFORE these ones:
this.getContextID();
this.getConsumerID();
this.getEnvType();
But you expect them to get executed in reverse order. No. Your getContextID method making a request to a server. Javascript is asynchronous. But, by using await expression in an asynchronous function, you can pause the execution and wait for the Promise.
So, just update your componentDidMount method as below:
async componentDidMount() {
await this.getContextID();
await this.getConsumerID();
await this.getEnvType();
this.setState({ populatedMultiSchema: this.multiSchema });
this.setState({ populatedMultiUISchema: this.multiUISchema });
}
Here i created a Codepen on usage of async/await. There are some details in comments. You can play with it as you want.
Even if your problem is not caused mainly by this, this approach is better. You should either use async/await or Promise to work with network requests.

Resources