React - Show loader on Click that already has function assigned to it - javascript

I have already a a click event within a ternary operator that does a GET request from my API. When the button is clicked, the button disappears and the data text replaces the button (button disappears). But there is a small gap of time between the get request and the text reveal. I want to put a loading message of some kind at that moment of time so the user knows something is happening. But can't seem to figure it out. Here is my code:
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
})
console.log(meal)
})
};
combinedFunction() {
this.fetchData();
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<div >
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
</div>
);
}
}
Appreciate the help.

What you can do is add a "isLoading" state and put the values before and after your API call like so:
fetchData() {
this.setState({isLoading: true});
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
isLoading: false,
})
console.log(meal)
})
};
And use that on your render to show the "loading icon"
render(){
const {isLoaded, meal, isLoading } = this.state;
return (
<div >
{isLoading ? <div>loading</div> :
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
}
</div>
);
}
}

This is a working demo which shows loading when api call starts and disables button to prevent multiple api calls. I added a 2sec time out to show the demo. Check the stackblitz sample
This is the updated code, here I used a fake api (https://jsonplaceholder.typicode.com/users) to show the demo
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
this.combinedFunction = this.combinedFunction.bind(this)
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
this.setState({
meal: res.data,
isLoaded: false
})
})
};
combinedFunction =()=> {
this.setState({isLoaded: true})
setTimeout(()=>{
this.fetchData();
},2000)
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<>
<div >
Users:
<button onClick={this.combinedFunction } disabled={isLoaded ? true : false}>{isLoaded ? 'Loading...':'Find User'}</button>
</div>
<div>
{meal && meal.map(item =>(
<div key={item.id}>
<p>{item.id} - {item.name}</p>
</div>
))}
</div>
</>
);
}
}

Related

TypeError: this.state.data.map in reactjs

class Home extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
};
}
componentDidMount() {
fetch("https://reqres.in/api/users?page=2")
.then((res) => res.json())
.then((json) => {
this.setState({
isLoaded: true,
data: json,
});
});
}
render() {
var { isLoaded, data }= this.state;
if(!isLoaded){
return<div>Is isLoaded</div>
}
else{
return (
<div>
<ul>
{() =>
this.state.data.map((data, index) => (
<li key={index}>Email: {data.email}</li>
))
}
;
</ul>
</div>
);
}
}
}
export default Home;
Hii All , I know this question is asked many times but I cant figure it out I'am getting the error. I have checked for all the questions similar to this but haven't found specific solution if I use another link i.e, "https://jsonplaceholder.typicode.com/users" this one the code works fine .
The returned data from https://reqres.in/api/users?page=2 is not an array, but an object with a data property containing what you are looking for (an array). The result of the request is :
{"page":1,"per_page":6,"total":12,"total_pages":2,"data":[{"id":1,"email":"george.bluth#reqres.in","first_name":"George","last_name":"Bluth","avatar":"https://reqres.in/img/faces/1-image.jpg"},{"id":2,"email":"janet.weaver#reqres.in","first_name":"Janet","last_name":"Weaver","avatar":"https://reqres.in/img/faces/2-image.jpg"},{"id":3,"email":"emma.wong#reqres.in","first_name":"Emma","last_name":"Wong","avatar":"https://reqres.in/img/faces/3-image.jpg"},{"id":4,"email":"eve.holt#reqres.in","first_name":"Eve","last_name":"Holt","avatar":"https://reqres.in/img/faces/4-image.jpg"},{"id":5,"email":"charles.morris#reqres.in","first_name":"Charles","last_name":"Morris","avatar":"https://reqres.in/img/faces/5-image.jpg"},{"id":6,"email":"tracey.ramos#reqres.in","first_name":"Tracey","last_name":"Ramos","avatar":"https://reqres.in/img/faces/6-image.jpg"}],"support":{"url":"https://reqres.in/#support-heading","text":"To keep ReqRes free, contributions towards server costs are appreciated!"}}
So you cannot use map function, which is from the Array prototype, on the result of your request. You must access the data property first :
this.state.data.data.map((data, index) => ( // note the double data
<li key={index}>Email: {data.email}</li>
))
You could also assign json.data to the state.data to avoid the ugly .data.data :
this.setState({
isLoaded: true,
data: json.data, // note the .data
});
I think the problem is in brackets around your .map() method. Please try this
class Home extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
};
}
componentDidMount() {
fetch("https://reqres.in/api/users?page=2")
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
data: json,
});
});
}
render() {
const { isLoaded, data } = this.state;
if (!isLoaded) {
return <div>Is isLoaded</div>;
} else {
return (
<div>
<ul>
{data?.map((data, index) => {
return <li key={index}>Email: {data.email}</li>;
})}
</ul>
</div>
);
}
}
}
export default Home;
I don't see any error, it's working just fine.
Output:
Working Example: StackBlitz
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
};
}
componentDidMount() {
fetch('https://reqres.in/api/users?page=2')
.then((res) => res.json())
.then((json) => {
console.log(json.data);
this.setState({
isLoaded: true,
data: json.data,
email: null,
});
});
}
render() {
var { isLoaded, data } = this.state;
if (!isLoaded) {
return <div>Is isLoaded</div>;
} else {
return (
<div>
<div className="contents home">
<img
src="https://trucard.io/india/wp-content/uploads/2021/08/2021-June-TruCard-Logo.png
"
width={50}
alt="img"
className="trucard-img"
/>
</div>
<div className="button">
<button className="button-button">Load list</button>
</div>
<ul>
{this.state.data?.map((data, index) => (
<li key={index}>Email: {data.email}</li>
))}
;
</ul>
</div>
);
}
}
}
export default App;

Value returned of a form with Stripe CardElement is undefined React Axios

I'm new to this and try to create a dropdown list with my product names and use the key of this products (I have a Django Backend) to then proceed to payment with Stripe.
The problem is that the value of my product selected in my dropdown list (ie the variable named SelectedPlan) passed down here is 'undefined' ...but when I pass the value like a string 'string_value_of_my_Stripe_product' for example everything is fine and the payment and subscription work correctly.
The problem seems to be with the fact that the state is not updated correctly to the value in the form ie e.target.value does not update when I select the product in the userform
...this part :
Axios
.post("../myAPI/blablabla", {
SelectedPlan: this.SelectedPlan,
})
Here are the 2 files I have ... SubscribeForm.js and a Billing.js.
I'm sorry there's maybe too much code but it's maybe necessary to get thw whole idea...I try to not show the unnecessary...
Thanks in advance !!!
Here is my SubscribeForm.js
import React from "react";
import {
CardElement,
injectStripe,
Elements,
StripeProvider,
}
const subscriptionOptions = [
{...values here},
];
class StripeForm extends React.Component {
state = {
loading: false,
error: null,
SelectedPlan: null,
};
onSelectedPlanChange = e => {
this.setState({
SelectedPlan: e.target.value,
});
};
submit = (ev) => {
ev.preventDefault();
this.setState({ loading: true, error: null });
if (this.props.stripe) {
this.props.stripe.createToken().then((result) => {
if (result.error) {
this.setState({
error: result.error.message,
loading: false,
SelectedPlan: null,
});
} else {
Axios
.post(".../myAPI/blablabla/", {
SelectedPlan: this.SelectedPlan,
})
.then((res) => {
this.setState({
loading: false,
});
this.props.handleUserDetails();
})
.catch((err) => {
console.log(err);
this.setState({
loading: false,
error: err.response.data.message,
});
});
}
});
} else {
console.log("Stripe js hasn't loaded yet");
}
};
render() {
const { loading, error } = this.state;
return (
<React.Fragment>
<Divider />
{error && <Message error header="There was an error" content={error} />}
<Form onSubmit={this.submit}>
<Form.Select
options={subscriptionOptions}
placeholder="SelectedPlan"
onChange={this.onSelectedPlanChange}
name="SelectedPlan"
value={this.SelectedPlan}
/>
<CardElement />
<Button
loading={loading}
disabled={loading}
type='submit'
>
Confirm
</Button>
</Form>
</React.Fragment>
);
}
}
const WrappedStripeForm = injectStripe(StripeForm);
class SubscribeForm extends React.Component {
render() {
return (
<StripeProvider apiKey="my_test_key">
<div>
<Elements>
<WrappedStripeForm {...this.props} />
</Elements>
</div>
</StripeProvider>
);
}
}
export default SubscribeForm;
Here is my Billing.js :
import React from "react";
import SubscribeForm from "./SubscribeForm";
class Billing extends React.Component {
state = {
...
};
componentDidMount() {
this.handleUserDetails();
}
...some code
handleUserDetails = () => {
this.setState({
loading: true,
});
Axios
.get(".../myAPI/mybilling/")
.then((res) => {
this.setState({
loading: false,
billingDetails: res.data,
});
console.log("billing details ici");
console.log(res.data);
})
.catch((err) => {
this.setState({
loading: false,
error: err.response.data.message,
});
});
};
renderBillingDetails(details) {
return (
<Segment>
...some code
<SubscribeForm handleUserDetails={this.handleUserDetails} />
...some code
</Segment>
);
}
...some other code
}
export default Billing;

React Bootstrap Typeahead specifies the length of characters in the input field

First problem: Why, if I enter one letter in input, console.log (this.state.results.length) does not show me 1. Only after entering two letters console.log (this.state.results.length) shows me 2.
Second problem: I will type three letters, then remove two letters, console.log (this.state.results.length) should show me 1 and show2. The same is when I clear the input it should show 0;
Demo here: https://stackblitz.com/edit/react-frleaq
class App extends Component {
constructor() {
super();
this.state = {
results: ''
};
}
_handleSearch = query => {
this.setState({
results: query
})
}
render() {
console.log(this.state.results.length)
return (
<div>
<AsyncTypeahead
clearButton
id="basic-example"
labelKey="name"
onSearch={this._handleSearch}
/>
</div>
);
}
}
You can use onInputChange to handle text changes and you can keep the text in state. This way you can check it's length and do whatever you want.
Example:
import React from "react";
import { AsyncTypeahead } from "react-bootstrap-typeahead";
import "bootstrap/dist/css/bootstrap.css";
import "react-bootstrap-typeahead/css/Typeahead.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
multiple: true,
options: [],
selectedUsers: [],
currentInput: ""
};
}
handleSearch = query => {
this.setState({ isLoading: true });
fetch(
`https://api.github.com/search/users?q=${query}+in:login&page=1&per_page=3`
)
.then(resp => resp.json())
.then(({ items }) => {
const options = items.map(i => ({
id: i.id,
name: i.login
}));
return { options };
})
.then(({ options }) => {
this.setState({
isLoading: false,
options
});
});
};
handleChange = selectedItems => {
this.setState({
selectedUsers: selectedItems,
currentInput: ""
});
};
handleInputChange = input => {
this.setState({
currentInput: input
});
};
render() {
const { selectedUsers, isLoading, options, currentInput } = this.state;
return (
<div>
<AsyncTypeahead
clearButton
id="basic-example"
isLoading={isLoading}
options={options}
minLength={3}
multiple
labelKey="name"
onSearch={query => this.handleSearch(query)}
onChange={selectedItems => this.handleChange(selectedItems)}
onInputChange={input => this.handleInputChange(input)}
placeholder="Search for users"
/>
<hr />
<br/>
<br/>
<br/>
{currentInput.length > 0 && <button>MY BUTTON</button>}
<hr />
Selected {selectedUsers.length} Users: <br />
<ul>
{selectedUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
}
export default App;

Simple loading before a success message

I'm trying to implement something really simple, where I want to display a loading between messages. A live example here
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
submitSuccess: false
};
}
onClick = () => {
console.log("click");
this.setState({
isLoading: false,
submitSuccess: true
});
};
render() {
return (
<div className="App">
<button onClick={this.onClick}>click</button>
{this.state.isLoading ? (
<p>loading...</p>
) : this.state.submitSuccess ? (
<p>sucess!</p>
) : (
<p>are you sure?</p>
)}
</div>
);
}
}
What I am trying to do is this example below:
Are you sure?
Loading...
Success!
But I'm not doing this right, since I'm not working properly with the ternary operators. How can I improve this?
Thanks! :)
Fixed here: https://codesandbox.io/s/rln4oz4vwq
Basic idea: Set loading to false by default, because you are not loading anything. On click of button, set loading to true as you truly are. Then on completion of async stuff, set loading to false and submitSuccess to true to indicate you are done.
Code:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
submitSuccess: false
};
}
onClick = () => {
console.log("click");
this.setState({
isLoading: true
});
//lets assume you now do some async stuff and after 2 seconds you are done
setTimeout(() => {
this.setState({
isLoading: false,
submitSuccess: true
});
}, 2000);
};
render() {
return (
<div className="App">
<button onClick={this.onClick}>click</button>
{this.state.isLoading ? (
<p>loading...</p>
) : this.state.submitSuccess ? (
<p>success!</p>
) : (
<p>are you sure?</p>
)}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

How to display posted data without refresh the page?

I have a simple react app, witch can GET and POST data to an API. It's a simple gallery where pics are categorized.
At first step I get all galleries from API. That's work fine.
class Home extends Component {
constructor(props) {
super(props);
this.state = {
galleries: [],
isLoading: false,
error: null,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch('http://.../gallery')
.then((response) => response.json())
.then((data)=>this.setState({galleries: data.galleries, isLoading: false}))
.catch(error => this.setState({ error, isLoading: false}));
}
render() {
const {galleries, isLoading, error} = this.state;
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <div className="loader-wrapper"><div className="loader"/></div>;
}
return (
<div className="categories">
{ galleries.length > 0 ? galleries.map((gallery) => {
return (
<Card key={gallery.path}>
...
</Card>
)}) : null
}
<AddCategory/>
</div>
);
}
}
At next step you can create new galleries.
class AddCategory extends Component {
constructor(props) {
super(props);
this.state = {
modal: false,
galleries: [],
isLoading: false,
error: null,
};
this.toggle = this.toggle.bind(this);
this.handleClick = this.handleClick.bind(this);
}
toggle() {
this.setState({
modal: !this.state.modal
});
}
handleClick(event) {
event.preventDefault();
this.setState({
modal: !this.state.modal
});
this.setState({ isLoading: true });
fetch('http://.../gallery', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({"name": this.galleryName.value})
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...')
}
})
.then((data)=>this.setState({galleries: data.galleries, isLoading: false}))
.catch(error => this.setState({ error, isLoading: false}));
}
render() {
const {modal, isLoading, error} = this.state;
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <div className="loader-wrapper"><div className="loader"/></div>;
}
return (
<Card className="add">
<div className="link" onClick={this.toggle}>
<CardBody>
<CardTitle>Add gallery</CardTitle>
</CardBody>
</div>
<Modal isOpen={modal} toggle={this.toggle} className={this.props.className}>
<div className="modal-header">
...
</div>
<ModalBody>
<form className="form-inline addCategoryForm">
<div className="group">
<input type="text" ref={(ref) => {this.galleryName = ref}} id="inputGalleryName" name="galleryName" required/>
<label>name of the gallery</label>
</div>
<Button onClick={this.handleClick} color="success">Add</Button>
</form>
</ModalBody>
</Modal>
</Card>
);
}
}
The problem is that after I click on Add button nothing happened on the page, but after I refresh the page the new gallery is in the list.
Do you have any idea why I get new gallery just after refresh the page, not immediately after click on button Add?
The reason why you cannot see new galleries in the list without refreshing is that the main component, in this case the Home component, is not being re-rendered since there isn't any change in its state variables, so it does not update the page. Your usage of this.setState after getting response, from POST method using fetch, only updates and re-renders sub component AddCategory.
Add commented sections below on your components to make Home component re-render.
For Home component;
class Home extends Component {
constructor(props) {
super(props);
this.state = {
galleries: [],
isLoading: false,
error: null,
};
// Add this method binding
this.updateGalleries = this.updateGalleries.bind(this);
}
// Add this method
updateGalleries = () => {
this.setState({ isLoading: true });
fetch('http://.../gallery')
.then((response) => response.json())
.then((data)=>this.setState({galleries: data.galleries, isLoading: false}))
.catch(error => this.setState({ error, isLoading: false}));
}
componentDidMount() {
...
}
render() {
...
return (
<div className="categories">
...
/* Add updateGalleries funtion as props to AddCategory */
<AddCategory updateGalleries={this.updateGalleries}/>
</div>
);
}
}
For AddCategory component;
class AddCategory extends Component {
constructor(props) {
...
}
toggle() {
...
}
handleClick(event) {
...
// edit this field after response.json()
.then((data)=>{
this.setState({galleries: data.galleries, isLoading: false})
this.props.updateGalleries();
})
.catch(error => this.setState({ error, isLoading: false}));
}
render() {
...
}
}

Categories

Resources