React - show loading message before fetched data is rendered - reactjs

I'm trying to build a react app that communicating with the server to query data from database. I want to display a loading message when the app is fetching data. Here's the code snippet:
class App extends React.Component {
constructor(props) {
super(props)
this.clickHandler = this.clickHandler.bind(this);
this.state = {
isLoading: false,
data: [],
content: ''
}
}
clickHandler() {
this.setState({ isLoading: true });
fetch('url_1')
.then(res => res.json())
.then(data => this.setState({ data: data }, () => {
this.getContentByData() // loading message vanished before content is displayed
}))
.then(() => this.setState({ isLoading: false }));
}
getContentByData() {
fetch(`url_2?data=${this.state.data}`)
.then(res => res.json())
.then(content => this.setState({ content: content }))
}
render() {
return (
<div>
{this.state.isLoading ? <h1>loading</h1>: null}
<h6>{this.state.content}</h6>
<button type="button" onClick={this.clickHandler}>Click Me!</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
The loading message disappeared before content is displayed. How can I fix this?

You could change your render method to be something like this:
render() {
let content = <Loader />;
if (!this.state.isLoading && this.state.content) {
content = (
<h6>{this.state.content}</h6>
<button type="button" onClick={this.clickHandler}>Click Me!</button>
)
}
return (
<div>
{content}
</div>
)
}
Where Loader is your loader component. Of course it can also be the h1 title or whatever you want.

you need to off your loading at getContentByData() like below.
getContentByData() {
fetch(`url_2?data=${this.state.data}`)
.then(res => res.json())
.then(content => this.setState({ content: content, isLoading : false }))
}
do not forget to remove isLoading=false in clickHandler() method.

isLoading will be set to false before the fetch inside getContentByData is complete, since it's not part of the promise chain in clickHandler.
You could instead set isLoading to false when the fetch in getContentByData is complete.
class App extends React.Component {
state = {
isLoading: false,
data: [],
content: ""
};
clickHandler = () => {
this.setState({ isLoading: true });
fetch("url_1")
.then(res => res.json())
.then(data => this.setState({ data }, this.getContentByData));
};
getContentByData = () => {
fetch(`url_2?data=${this.state.data}`)
.then(res => res.json())
.then(content => this.setState({ isLoading: false, content }));
}
// ...
}

Or even better you can render something like this:
render() {
return (
<div>
{this.state.isLoading && !this.state.content && <Loader />}
{!this.state.isLoading && this.state.content && (
<Fragment>
<h6>{this.state.content}</h6>
<button type="button" onClick={this.clickHandler}>Click Me!</button>}
</Fragment>)
</div>
)
}

Related

How to change let value onClick in React

My default value is null (let activestatus = "";), but I want it to change on click to be:
let activestatus = "?IsActive=0";
I am getting value on click (as seen in console), but the value is not passed in "let activestatus".
class App extends Component {
state = {
reservations: [],
};
componentWillMount() {
let activestatus = "";
axios
.get("https://localhost:44307/api/GetReservations/" + `${activestatus}`)
.then((response) => {
this.setState({
reservations: response.data,
});
});
}
showActive = (e) => {
e.preventDefault();
console.log(e.target.value);
this.activestatus = e.target.value;
};
render() {
let reservations = this.state.reservations.map((reservation) => {
return (
<tr>
<td>{reservation.Id}</td>
</tr>
);
});
return (
<div className="App container">
<Button
class="activity-button"
value={"?IsActive=0"}
id="active"
onClick={this.showActive}
>
Can you try to have activeStatus as part of your state? Also if you want to refresh the data from the api based on this field, then should probably use componentDidUpdate that runs on state changes.
class App extends Component {
state = {
reservations: [],
activestatus: ""
};
componentWillMount() {
axios
.get("https://localhost:44307/api/GetReservations/" + `${activestatus}`)
.then((response) => {
this.setState({
reservations: response.data,
});
});
}
showActive = (e) => {
e.preventDefault();
console.log(e.target.value);
this.setState({ activestatus: e.target.value });
};
render() {
let reservations = this.state.reservations.map((reservation) => {
return (
<tr>
<td>{reservation.Id}</td>
</tr>
);
});
return (
<div className="App container">
<Button
class="activity-button"
value={"?IsActive=0"}
id="active"
onClick={this.showActive}
>`
Thanks guys, both were helpful.
Solution:
class App extends Component {
state = {
reservations: [],
activestatus: "",
};
componentDidUpdate() {
axios
.get(
"https://localhost:44307/api/GetReservations/" +
`${this.state.activestatus}`
)
.then((response) => {
this.setState({
reservations: response.data,
});
});
}
}
showActive = (e) => {
e.preventDefault();
console.log(e.target.value);
this.setState({ activestatus: e.target.value });
};
render() {
let reservations = this.state.reservations.map((reservation) => {
return (
<tr>
<td>{reservation.Id}</td>
</tr>
);
});
return (
<div className="App container">
<Button
class="activity-button"
value={"?IsActive=0"}
id="active"
onClick={this.showActive}
>`

How I do use fetch API and store response in the state?

I have to get a file from the server, After the component is rendered, that contains information from cities, and I must assign it to "citiesData" in the state. But the data is not received because it is not seen in the output.
what is the issue with my fetch?
server file:
IranMap(the file seems to have a problem in fetch):
import React from 'react';
import './IranMap.css';
import CityModal from './CityModal';
class IranMap extends React.Component {
state = {
error: null,
citiesData: null,
selectedCity: null,
isModalOpen: false,
};
componentDidMount() {
fetch('http://localhost:9000/cities')
.then(response => response.json())
.then((result) => {
this.setState({
citiesData: result
});
},
(error) => {
this.setState({
error
});
}
)
}
cityClicked = (id) => (event) => {
event.preventDefault();
fetch(`http://localhost:9000/cities/${id}`,{method: 'GET'})
.then(res => res.json())
.then(result => {
this.setState({
selectedCity: result,
isModalOpen: true
});
})
}
closeModal = () => {
this.setState({
isModalOpen: false,
});
};
render() {
if(this.state.error){
return <div>Error: {this.state.error.message}</div>;
}
else {
return (
<div>
<div className="map-container">
{(this.state.citiesData || []).map((record) => (
<div
key={record.id}
className="city-name"
style={{
top: `${record.top}%`,
left: `${record.left}%`,
}}
onClick={this.cityClicked(record.id)}
>
{record.name}
</div>
))}
</div>
<CityModal
city={this.state.selectedCity}
isOpen={this.state.isModalOpen}
onClose={this.closeModal}
/>
</div>
);
}
}
}
export default IranMap;
This is my output. it should be show cities name. but this is empty:
I think what you are trying to do is render the entire object,u cant do that, try the render each element, The second part of my answer is that you should use an asynchronous task.
I hope my answer guided you

How to setState to answer from APi and use map

Im trying to create recipes searcher. In App.js I receive query from search input from another component and I want to setState to answer from APi. Console.log from callback in setState shows updated state but the state is not updated. I need setState updaed so I can use map on it and display list of recipes in render. It gives me error map is not a function because this.state.recipesList is still empty. Anyone can help me ?
class App extends Component {
state = {
query: "",
recipesList: []
};
getQuery = query => {
const key = "2889f0d3f51281eea62fa6726e16991e";
const URL = `https://www.food2fork.com/api/search?key=${key}&q=${query}`;
fetch(URL)
.then(res => res.json())
.then(res => {
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
});
console.log(this.state.recipesList);
};
render() {
const test = this.state.recipesList.map(item => {
return (
<div className="recispesList">
<h1>{item.title}</h1>
</div>
);
});
return (
<div className="App">
<Search query={this.getQuery} />
<div className="contentWrapper">{}</div>
</div>
);
}
}
Search component:
class Search extends Component {
state = {
searchValue: ""
};
handleChange = val => {
let searchValue = val.target.value;
this.setState({
searchValue
});
};
handleSubmit = e => {
e.preventDefault();
this.setState({
searchValue: ""
});
this.props.query(this.state.searchValue);
};
render() {
return (
<div className="searchWrapper">
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.searchValue} />
<button />
</form>
</div>
);
}
}
export default Search;
It seems that instead of directly assigning the whole response to recipesList:
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
you need to get recipes array first via res.recipes:
this.setState(
{
recipesList: res.recipes
},
() => {
console.log(this.state.recipesList);
}
);

Reactjs Event/Action Button not switching as expected

Reactjs Event/Action Button not switching as expected.
Am trying to add follow and unfollow action button. when I post via axios via Follow button,
it post to data to server backend and return success message. Then the Follow button switched to Unfollow button.
Now my problem is that Unfollow button is not switching back to Follow Button when User try to unfollow someone.
Please what am I doing wrong here.
here is the json success message
[{"status":"success", "follow":"1", "unfollow":"0"}]
here is the my code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
result_data: '',
data: [],
loading: false
};
}
componentDidMount() {
this.setState({
data: [{"uid":"1","name":"Nancy"},{"uid":"2","name":"Moore"}],
});
}
// update user following
handleFollowUser(user_id) {
const uid_data = { user_id: user_id };
axios
.get("http://localhost/data.json", { uid_data })
.then(response => {
this.setState(state => ({
//data: newData,
result_data: response.data[0].status
}));
alert(result_data);
})
.catch(error => {
console.log(error);
});
}
// update user unfollowing
handleUnFollowUser(user_id) {
const uid_data = { user_id: user_id };
axios
.get("http://localhost/data.json", { uid_data })
.then(response => {
this.setState(state => ({
//data: newData,
result_data: response.data[0].status
}));
alert(result_data);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<span>
<label>
<ul>
<h1>Users</h1> <br />
{this.state.result_data }
{this.state.data.map((users) => {
return (
<div key={users.uid}>
<div>
<b> Name: </b>{users.name}
<br />
{this.state.result_data === ''
? <span onClick={() =>
this.handleFollowUser(users.uid)}>Follow</span>
: <span onClick={() =>
this.handleUnFollowUser(users.uid)}>unfollow</span>
}
</div>
</div>
)
}
)}
</ul>
</label>
</span>
);
}
}
This is what solved my Reactjs problem.I first initialize
isToggleOn: !state.isToggleOn in the click event and in the constructor I implemented
this.state = {isToggleOn: true};
My click button becomes
<button onClick={this.handleFollowUser}>
{this.state.isToggleOn ? 'Follow' : 'Unfollow'}
</button>

Reactjs displays error users.map is not a function

I am getting the following error in my React application:
users.map is not a function
I have tried many solutions posted on Stack Overflow but it does not seems to solve my issue.
I used the code below to submit a filename and it works fine. Here is my problem. Each time i click on submit button, i
want to display a JSON data from the backend in a succession (For Instance If I submit form 3 times, I need to have 3 records of JSON data showed).
Here is the sample of JSON:
{"filename":"macofile","message":"success","uid":"20"}
To this effect I have set the following line of code in the Axios Post response
this.setState({
users: res.data,
loading: false,
});
I have also tried
users: res
or
users.push(res.data);
This is my code:
import React, { Component } from "react";
import axios, { post } from "axios";
class FilePage extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
filename: "",
loading: false,
users: [],
error: null
};
this.handleChange = this.handleChange.bind(this);
}
_handleSubmit(e) {
e.preventDefault();
//send it as form data
const formData = new FormData();
formData.append("filename", this.state.filename);
//alert(this.state.filename);
this.setState({ loading: true }, () => {
axios
.post("http://localhost/apidb_react/up.php", formData)
.then(res => {
this.setState({
users: res.data,
loading: false
});
})
.catch(err => {
console.log(err.message);
});
});
}
// handle form submission
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
render() {
const { loading, users, error } = this.state;
return (
<div>
<form onSubmit={e => this._handleSubmit(e)}>
<b>filename:</b>
<input
tyle="text"
className="form-control"
value={this.state.filename}
name="filename"
onChange={this.handleChange}
/>
<button
className="submitButton"
type="submit"
onClick={e => this._handleSubmit(e)}
>
submit
</button>
</form>
<React.Fragment>
<h3>Display Data each time record is submitted</h3>
{error ? <p>{error.message}</p> : null}
{!loading ? (
users.map(user => {
const { filename, message, uid } = user;
return (
<div key={uid}>
<p>Userid: {uid}</p>
<p>File Name: {filename}</p>
<p>Message: {message}</p>
<hr />
</div>
);
})
) : (
<h3>Loading...</h3>
)}
</React.Fragment>
</div>
);
}
}
Your res.data seems to be an object rather than an array -> {"filename":"macofile","message":"success","uid":"20"}. So, you will need to loop through the object by taking an array for eg:
Object.keys(users).map(key => console.log(users[key]))

Resources