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
});
}
);
}
Related
I have get my data from firebase , loop through them and display them to dom.
then I added a delete button and send a delete request using axios and it's delete from firebase but the dom doesn't rerender. I set a deleting state to change it in 'then' block but even when I change the state it dosn't rerender!
what can I do?
class Orders extends Component {
state = {
orders: [],
loading: true,
deleting: false,
};
componentDidMount() {
axios
.get('/order.json')
.then((res) => {
// console.log(res.data);
const fetchedOrders = [];
for (let key in res.data) {
fetchedOrders.push({ ...res.data[key], id: key });
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch((err) => {
this.setState({ loading: false });
});
}
deleteHandler = (id) => {
axios.delete(`/order/${id}.json`).then((res) => {
this.setState({ deleting: true });
console.log(res, this.state.deleting);
});
};
render() {
return (
<div>
{this.state.orders.map((order) => (
<Order
key={order.id}
ingredients={order.ingredient}
price={order.price}
id={order.id}
delete={() => this.deleteHandler(order.id)}
/>
))}
</div>
);
}
}
You have to update the orders state while calling deleteHandler! Try this code!
import React from 'react';
import axios from 'axios';
// YOUR OTHER IMPORT GOES HERE
class Orders extends Component {
constructor(props) {
this.state = {
orders: [],
loading: true,
deleting: false,
}
}
componentDidMount() {
axios
.get('/order.json')
.then((res) => {
// console.log(res.data);
const fetchedOrders = [];
for (let key in res.data) {
fetchedOrders.push({ ...res.data[key], id: key });
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch((err) => {
this.setState({ loading: false });
});
}
deleteHandler = (id) => {
this.setState({
orders: this.state.orders.filter(orderValue => orderValue.id !== id)
})
axios.delete(`/order/${id}.json`).then((res) => {
this.setState({ deleting: true });
console.log(res, this.state.deleting);
});
};
render() {
return (
<div>
{this.state.orders.map((order) => (
<Order
key={order.id}
ingredients={order.ingredient}
price={order.price}
id={order.id}
delete={() => this.deleteHandler(order.id)}
/>
))}
</div>
);
}
}
I'm trying to write a code that shows a list of images, etc. based on this answer:
https://stackoverflow.com/a/57635373/9478434
My code is:
class ImageGallery extends Component {
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
}
getImages() {
axios
.get(IMAGE_LIST_URL, {})
.then((response) => {
const data = response.data;
console.log(data);
this.setState({ imageList: response.data });
})
.catch((error) => {
setTimeout(() => {
console.log(error.response.data.message);
}, 200);
});
}
componentDidMount() {
this.getImages();
}
changePhotoIndex(imgIndex) {
this.setState({ photoIndex: imgIndex, isOpen: true });
}
render() {
const { photoIndex, isOpen, imageList } = this.state;
const singleImage = imageList.map(function (img, imgIndex) {
const imagePath = `http://localhost:8000/media/${img.filePath}`;
console.log(imagePath);
return (
<figure className="col-xl-3 col-sm-6">
<img
src={imagePath}
alt="Gallery"
className="img-thumbnail"
onClick={() => this.changePhotoIndex(imgIndex)}
/>
</figure>
);
});
return <div>{singleImage}</div>;
}
}
However while clicking on the image, I get a type error (t is undefined) in the console regarding to the line onClick={() => this.changePhotoIndex(imgIndex) and the state of app does not change.
The changePhoneIndex function considers itself and not the component as this.
You can bind it to the component itself and be able to access setState by adding this to the constructor:
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
this.changePhotoIndex.bind(this);
}
Or you can call set state directly:
onClick={() => this.setState({ photoIndex: imgIndex, isOpen: true })}
You forgot to bind your function. Add the following to your code:
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
this.changePhotoIndex = this.changePhotoIndex.bind(this); // missing
}
I am trying to get my photo blog/phlog manager component functional. However the console says the there an undefined object through props.
import React, { Component } from 'react';
import axios from 'axios';
import DropzoneComponent from 'react-dropzone-component';
import "../../../node_modules/react-dropzone-component/styles/filepicker.css";
import "../../../node_modules/dropzone/dist/min/dropzone.min.css";
class PhlogEditor extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
phlog_status: '',
phlog_image: '',
editMode: false,
position: '',
apiUrl: 'http://127.0.0.1:8000/phlogapi/phlog/',
apiAction: 'post'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.componentConfig = this.componentConfig.bind(this);
this.djsConfig = this.djsConfig.bind(this);
this.handlePhlogImageDrop = this.handlePhlogImageDrop.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.phlogImageRef = React.createRef();
}
deleteImage(event) {
event.preventDefault();
axios
.delete(
`http://127.0.0.1:8000/phlogapi/phlog/${this.props.id}/delete`,
{ withCredentials: true }
)
.then(response => {
this.props.handlePhlogImageDelete();
})
.catch(error => {
console.log('deleteImage failed', error)
});
}
The error is occuring at Object.keys(this.props.phlogToEdit).length>0
componentDidUpdate() {
if (Object.keys(this.props.phlogToEdit).length > 0) {
// debugger;
const {
id,
phlog_image,
phlog_status,
position
} = this.props.phlogToEdit;
this.props.clearPhlogsToEdit();
this.setState({
id: id,
phlog_image: phlog_image || '',
phlog_status: phlog_status || '',
position: position || '',
editMode: true,
apiUrl: `http://127.0.0.1:8000/phlogapi/phlog/${this.props.id}/update`,
apiAction: 'patch'
});
}
}
handlePhlogImageDrop() {
return {
addedfile: file => this.setState({ phlog_image_url: file })
};
}
componentConfig() {
return {
iconFiletypes: [".jpg", ".png"],
showFiletypeIcon: true,
postUrl: "https://httpbin.org/post"
};
}
djsConfig() {
return {
addRemoveLinks: true,
maxFiles: 3
};
}
buildForm() {
let formData = new FormData();
formData.append('phlog[phlog_status]', this.state.phlog_status);
if (this.state.phlog_image) {
formData.append(
'phlog[phlog_image]',
this.state.phlog_image
);
}
return formData;
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
axios({
method: this.state.apiAction,
url: this.state.apiUrl,
data: this.buildForm(),
withCredentials: true
})
.then(response => {
if (this.state.phlog_image) {
this.phlogImageRef.current.dropzone.removeAllFiles();
}
this.setState({
phlog_status: '',
phlog_image: ''
});
if (this.props.editMode) {
this.props.handleFormSubmission(response.data);
} else {
this.props.handleSuccessfulFormSubmission(response.data);
}
})
.catch(error => {
console.log('handleSubmit for phlog error', error);
});
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit} className='phlog-editor-wrapper'>
<div className='one-column'>
<div className='image-uploaders'>
{this.props.editMode && this.props.phlog_image_url ? (
<div className='phlog-manager'>
<img src={this.props.phlog.phlog_image_url} />
<div className='remove-image-link'>
<a onClick={() => this.deleteImage('phlog_image')}>
Remove Photos
</a>
</div>
</div>
) : (
<DropzoneComponent
ref={this.phlogImageRef}
config={this.componentConfig()}
djsConfig={this.djsConfig()}
eventHandlers={this.handlePhlogImageDrop()}
>
<div className='phlog-msg'>Phlog Photo</div>
</DropzoneComponent>
)}
</div>
<button className='btn' type='submit'>Save</button>
</div>
</form>
);
}
}
export default PhlogEditor;
I do not understand how the object is empty when the props are coming from the parent component
phlog-manager.js:
import React, { Component } from "react";
import axios from "axios";
import PhlogEditor from '../phlog/phlog-editor';
export default class PhlogManager extends Component {
constructor() {
super();
Here I define phlogToEdit as an object to pass as props to phlogEditor child component
this.state = {
phlogItems: [],
phlogToEdit: {}
};
this.handleNewPhlogSubmission = this.handleNewPhlogSubmission.bind(this);
this.handleEditPhlogSubmission = this.handleEditPhlogSubmission.bind(this);
this.handlePhlogSubmissionError = this.handlePhlogSubmissionError.bind(this);
this.handleDeleteClick = this.handleDeleteClick.bind(this);
this.handleEditClick = this.handleEditClick.bind(this);
this.clearPhlogToEdit = this.clearPhlogToEdit.bind(this);
}
clearPhlogToEdit() {
this.setState({
phlogToEdit: {}
});
}
handleEditClick(phlogItem) {
this.setState({
phlogToEdit: phlogItem
});
}
handleDeleteClick(id) {
axios
.delete(
`http://127.0.0.1:8000/phlogapi/phlog/${id}`,
{ withCredentials: true }
)
.then(response => {
this.setState({
phlogItems: this.state.phlogItems.filter(item => {
return item.id !== id;
})
});
return response.data;
})
.catch(error => {
console.log('handleDeleteClick error', error);
});
}
handleEditPhlogSubmission() {
this.getPhlogItems();
}
handleNewPhlogSubmission(phlogItem) {
this.setState({
phlogItems: [phlogItem].concat(this.state.phlogItems)
});
}
handlePhlogSubmissionError(error) {
console.log('handlePhlogSubmissionError', error);
}
getPhlogItems() {
axios
.get('http://127.0.0.1:8000/phlogapi/phlog',
{
withCredentials: true
}
)
.then(response => {
this.setState({
phlogItems: [...response.data]
});
})
.catch(error => {
console.log('getPhlogItems error', error);
});
}
componentDidMount() {
this.getPhlogItems();
}
render() {
return (
<div className='phlog-manager'>
<div className='centered-column'>
This is where the object, phlogToEdit is being passed as props to child component mentioned
<PhlogEditor
handleNewPhlogSubmission={this.handleNewPhlogSubmission}
handleEditPhlogSubmission={this.handleEditPhlogSubmission}
handlePhlogSubmissionError={this.handleEditPhlogSubmission}
clearPhlogToEdit={this.clearPhlogToEdit}
phlogToEdit={this.phlogToEdit}
/>
</div>
</div>
);
}
}
#Jaycee444 solved the problem it was the parent component!
phlogToEdit={this.state.phlogToEdit}
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() {
...
}
}
I'm trying to do pagination by clicking on some text that calls a method to increment the state value. The state value then gets passed to the axios call which should then call the next page. I'm noticing however that while the state is getting increment in a console.log from the render function, the axios call is not getting called again with the new state value. Anyone have any idea how I can fix this?
constructor(props) {
super(props);
this.state = {
people: [],
planets: [],
page: 1
};
this.pageIncrementer = this.pageIncrementer.bind(this);
}
componentWillMount() {
let page = this.state.page;
axios({
method: 'GET',
url: `http://localhost:3008/people?_page=${page}&_limit=10`
}).then((response) => {
this.setState({
people: response
});
}).catch((error) => {
console.log('There is an error in the Card axios call for people: ', error);
})
axios({
method: 'GET',
url: `http://localhost:3008/planets?_page=${page}&_limit=10`
}).then((response) => {
this.setState({
planets: response
});
}).catch((error) => {
console.log('There is an error in the Card axios call for planets: ', error);
})
}
pageIncrementer() {
this.setState({
page: this.state.page + 1
});
}
componentWillMount called only once, you need componentDidUpdate
https://facebook.github.io/react/docs/react-component.html#componentdidupdate
let getData = () => Math.random();
class Example extends React.Component{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
name: ''
};
}
componentWillMount(){
console.log('componentWillMount')
}
componentDidUpdate(){
console.log('componentDidUpdate')
}
handleChange(e) {
this.setState({
name: this.props.getData()
});
}
render() {
return <div className="widget">
{this.state.name}
<button onClick={this.handleChange}>Inc</button>
</div>;
}
}
React.render(<Example getData={getData}/>, document.getElementById('container'));
Edit(alternative way):
let getData = () => Math.random();
class Example extends React.Component{
constructor(props) {
super(props);
this.makeRequest = this.makeRequest.bind(this)
this.state = {
page:1,
name:''
};
}
makeRequest(next){
fetch('https://jsonplaceholder.typicode.com/posts/'+this.state.page)
.then(
result => {
console.log('do')
return result.json()}
)
.then(
(resp) => this.setState({
name:resp, page:this.state.page+1})
)
}
render() {
return <div className="widget">
{this.state.name}
<button onClick={this.makeRequest}>Request</button>
</div>;
}
}
React.render(<Example getData={getData}/>, document.getElementById('container'));