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() {
...
}
}
Related
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;
In 1 component, when I click on the picture, I get its id, which I pass to another component via props. I need to receive these props every time and send a feth - request with the id of the image and then redraw the component. How to do it correctly?
first component
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
isOpen: false,
images: [],
idImg: ''
};
}
openModal = (e) => {
this.setState({ isOpen: true, idImg: e.target.id });
}
render() {
const {error, isLoaded, images} = this.state;
if (error) {
return <p>Error</p>
} else if (!isLoaded) {
return <p> Loading ... </p>
} else {
return (
<div className="row align-items-center m-4" onChange={this.onSelect}>
<Modal
isOpen={this.state.isOpen}
onCancel={this.handleCancel}
onSubmit={this.handleSubmit}
idImg={this.state.idImg}
></Modal>
{images.map(item => (
<div key={item.image_id} className="col-lg-4 col-lg-4 sm-1 p-2" style={{Style}} >
<img id={item.image_id} src={item.src} alt={item.src} onClick={this.openModal}></img>
</div>
))}
</div>
)
}
}
2 component:
export default class Modal extends Component {
constructor(props){
super(props);
this.state = {
imgSrc: ' ',
commentList: [],
_id: this.props.idImg
}
}
componentDidMount(){
fetch(`./api/${this.state._id}`, {
method: 'GET',
})
.then(res => res.json())
.then((result) => {
this.setState({
isLoaded: true,
imgSrc: result.src
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
Factor out the fetch into a utility function that can be called in componentDidMount and componentDidUpdate when the props update.
Also, don't store passed props into local component state, this is an anti-pattern in react. You can simply consume the passed idImg prop in the lifecycle methods.
export default class Modal extends Component {
constructor(props){
super(props);
this.state = {
imgSrc: ' ',
commentList: [],
}
}
fetchImage = imageId => {
this.setState({ isLoaded: false }); // <-- set loading state
fetch(`./api/${imageId}`, {
method: 'GET',
})
.then(res => res.json())
.then((result) => {
this.setState({
isLoaded: true,
imgSrc: result.src
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
};
componentDidMount() {
this.fetchImage(this.props.idImg); // <-- pass idImg prop
}
componentDidUpdate(prevProps) {
if (prevProps.idImg !== this.props.idImg) { // <-- compare idImg values
this.fetchImage(this.props.idImg); // <-- pass idImg prop
}
}
export default class Modal extends Component {
constructor(props){
super(props);
this.state = {
imgSrc: ' ',
commentList: [],
_id: this.props.idImg
}
this.nameFunction=this.nameFunction.bind(this);
}
componentDidMount(){
this.nameFunction();
}
componentDidUpdate(prevProps) {
if (prevProps.idImg!== this.props.idImg) {
this.setState({
_id: this.props.idImg,
})
}
}
nameFunction(){
fetch(`./api/${this.state._id}`, {
method: 'GET',
})
.then(res => res.json())
.then((result) => {
this.setState({
isLoaded: true,
imgSrc: result.src
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
}
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;
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>
</>
);
}
}
My app has an onClick that should be rendering more gifs. However, it does it once and then stops. Also, the onClick deletes all the gifs that were already on the page. Anyone know what I'm doing wrong?
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
};
}
componentDidMount() {
this.searchGifs('kittens');
}
searchGifs = (searchQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${searchQuery}&limit=12&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
results: response.data,
});
});
}
searchMoreGifs = (offsetQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${offsetQuery}&limit=12&offset=${this.state.results.length}&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
results: response.data,
});
});
}
render() {
return (
<main className="app">
<Header />
<SearchForm startSearch={this.searchGifs} />
<ResultList gifs={this.state.results} />
<LoadMore gifs={this.state.results} searchMore={this.searchMoreGifs} />
</main>
);
}
}
and here is the onClick:
class LoadMore extends React.Component {
render(props) {
return(
<button onClick={this.props.searchMore}>Load More</button>
);
}
}
export default LoadMore;
Each time you call this.setState({results: something}) you completely overwrite the previous state of results.You want to take the array that is in this.state.results and concat it with the new results.
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
// I also suggest moving the searchQuery to the state so it can be used in both the offset and the original search
searchQuery: 'kittens'
};
}
componentDidMount() {
this.searchGifs(this.state.searchQuery);
}
searchGifs = (searchQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${searchQuery}&limit=12&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
results: response.data,
});
});
}
searchMoreGifs = (offsetQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${offsetQuery}&limit=12&offset=${this.state.results.length}&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
// You were overwriting the old results with new data every time you ran this function
results: this.state.results.concat(response.data),
});
});
}
render() {
return (
<main className="app">
<Header />
<SearchForm startSearch={this.searchGifs} />
<ResultList gifs={this.state.results} />
{/* You also need to pass the offsetQuery to the LoadMore component so that the searchMore function can use it*/}
<LoadMore searchMore={this.searchMoreGifs} offsetQuery={this.state.searchQuery} />
</main>
);
}
}
class LoadMore extends React.Component {
render(props) {
const {offsetQuery, searchMore} = this.props
return (
<button onClick={() => searchMore(offsetQuery)}>Load More</button>
);
}
}
export default LoadMore;