How to rerender DOM after component's state was changed? - reactjs

I am making a request to axios and receiving some data, which then I setState to my component's state:
componentDidMount() {
instance
.get("https://bartering-application.firebaseio.com/myitems.json")
.then(response => {
var obj = Object.values(response.data);
console.log("parsed", obj);
this.setState({ addedItem: obj });
})
.catch(error => {
console.log(error);
});
}
So my state, which had state property addedItem now gets objs as value.
Then, in my render() method I am rendering a child component, which receives props from my state(whose properties updated through componentDidMount):
render() {
const items = this.state.addedItem.map(item => {
return (
<MyItem
title={item.Title}
description={item.Description}
condition={item.Condition}
url={item.URL}
/>
)
})
}
This works fine, however I can see the result of child component displayed, only if I reload the browser. How can I make the app reload automatically whenever a state property (in my case addedItem) changes ? Which lifecycle method should I use to rerender the DOM immidiately when the state property changes ?
The full component code is below:
class MyItems extends Component {
constructor(props) {
super(props);
const initial_state = {
image: null,
url: "",
uploadStatus: false,
itemTitle: "",
itemDescription: "",
barteringCondition: "",
addedItem: []
};
this.state = initial_state;
this.handleChange = this.handleChange.bind(this);
this.handleUpload = this.handleUpload.bind(this);
}
componentDidMount() {
instance
.get("https://bartering-application.firebaseio.com/myitems.json")
.then(response => {
var obj = Object.values(response.data);
console.log("parsed", obj);
this.setState({ addedItem: obj });
})
.catch(error => {
console.log(error);
});
}
// componentDidUpdate(prevState){
// if (prevState !== this.state){
// window.location.reload();
// }
// }
handleChange = e => {
if (e.target.files[0]) {
const image = e.target.files[0];
this.setState(
() => ({ image, uploadStatus: true }),
() => console.log(this.state.image.name)
);
}
};
handleUpload = () => {
if (!this.state.uploadStatus) {
alert("No item image was uploaded.");
return null;
}
const { image } = this.state;
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
"state_changed",
snapshot => {
// demonstrate the image upload progress
},
error => {
// error function
console.log(error);
},
() => {
//complete function
storage
.ref(`images`)
.child(image.name)
.getDownloadURL()
.then(url => {
console.log(url);
alert("uploaded!");
this.setState({ url });
// When uploadded image url is received, collect all item data into myNewItem object and post this record to Firebase Database
const myNewItem = {
Title: this.state.itemTitle,
Description: this.state.itemDescription,
URL: this.state.url,
Condition: this.state.barteringCondition
};
instance.post("/myitems.json", myNewItem).then(error => {
console.log(error);
});
});
}
);
};
titleChangeHandler = event => {
this.setState({ itemTitle: event.target.value });
};
descriptionChangeHandler = event => {
this.setState({ itemDescription: event.target.value });
};
render() {
const items = this.state.addedItem.map(item => {
return (
<MyItem
title={item.Title}
description={item.Description}
condition={item.Condition}
url={item.URL}
/>
);
});
return (
<Auxiliary>
<div className={classes.MyItems}>
<div className={classes.container}>
<div className={classes.MyItems__left__container}>
<div className={classes.Items__Upload}>
{" "}
<p>Upload your barter item picture below:</p>
<br />
<input type="file" onChange={this.handleChange} />
<br />
<p style={{ padding: "0px", margin: "10px" }}>
Title of the item:
</p>
<input type="text" onChange={this.titleChangeHandler} />
</div>
<div className={classes.Items__Info}>
<div className={classes.Items_Description}>
<p>Describe your item:</p>
<textarea
rows="15"
cols="30"
onChange={this.descriptionChangeHandler}
/>
</div>
<div className={classes.Items_Bartering__Condition}>
<p>Bartering condition:</p>
<br />
<div className={classes.Items__Bartering_Condition_Options}>
<fieldset id="barter-options">
<input type="radio" name="with-similar" />
With a similar item <br />
<input type="radio" name="with-similar-with-extra" />
With a similar item with extra payment <br />
<input type="radio" name="with" />
With
<input
style={{ height: "11px", maxWidth: "240px" }}
type="text"
name="special-item"
placeholder="e.g. Rolex Watch model 16233"
/>
<br />
<input type="radio" name="as-gift" />I give this item as
gift! <br />
<input type="radio" name="as-gift" />I give this item as
gift to
<input
style={{ height: "11px", maxWidth: "120px" }}
type="text"
placeholder="e.g. students"
/>
<br />
</fieldset>
<div className={classes.Items_addButton}>
<button onClick={this.handleUpload}>+ADD</button>
</div>
</div>
</div>
</div>
</div>
<div className={classes.MyItems__right__container}>
<div className={classes.MyItems__right__container__header}>
<p>My items</p>
</div>
<div className={classes.MyItems__right__container__block}>
{/* <MyItem title={this.state.itemTitle} description={this.state.itemDescription} condition={this.state.barteringCondition} url={this.state.url} /> */}
{items}
</div>
</div>
</div>
</div>
</Auxiliary>
);
}
}
export default MyItems;
the child MyItem component:
import React, { useEffect } from "react";
import Auxiliary from "../hoc/Auxiliary";
import { storage } from "../Firebase/Fire";
import classes from "../MyItem/MyItem.module.css";
const MyItem = props => {
return (
<Auxiliary>
<div className={classes.MyItem}>
<h4>Item: {props.title}</h4>
<img
src={props.url || "https://via.placeholder.com/140x100"}
height="100"
width="140"
/>
<p>Description: {props.description}</p>
<p>Bartering condition: {props.condition}</p>
</div>
</Auxiliary>
);
};
export default MyItem;

I solved this by adding window.location.reload() method, which triggers the rerendering of DOM immediately after the user image was successfully uploaded to the FIrebase Database. So here is the code:
instance.post('/myitems.json', myNewItem)
.then(response => {window.location.reload();} )
.then(error => {
console.log(error);
})

Firebase will gives you the response in the form of object. We need to convert this to array and use it. Try the below logic at the start of your render function:
const fetchedItems = [];
for(let key in this.state.addedItem) {
fetchedItems.push({
...this.state.addedItem[key],
id: key
});
}
const items = fetchedItems.map(item => {
return (
<MyItem
title={item.Title}
description={item.Description}
condition={item.Condition}
url={item.URL}
/>
);
});

Related

React - Merging two Component Values

Building a simple ToDo list app in ReactJS. This page is where an existing task can be edited:
import React, {useState, useEffect} from "react";
import {PageLayout} from "../components/page-layout";
import {useParams, useNavigate} from 'react-router-dom';
import TaskDataService from "../services/TaskService";
import DatePicker from 'react-datepicker';
import FadeIn from 'react-fade-in';
import UserDataService from "../services/UserService";
export const Task = props => {
const {id} = useParams();
let navigate = useNavigate();
const initialTaskState = {
id: null,
familyId: "",
userId: "",
ownerId: "",
ownerName: "",
title: "",
description: "",
completed: false,
dueDate: new Date()
};
const [currentTask, setCurrentTask] = useState(initialTaskState);
const [message, setMessage] = useState("");
const [members, setMembers] = useState([]);
const [currentMember, setCurrentMember] = useState(null);
const [currentIndex, setCurrentIndex] = useState(-1);
const getTask = id => {
TaskDataService.get(id)
.then(response => {
setCurrentTask(response.data);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
retrieveMembers();
}, []);
useEffect(() => {
if (id)
getTask(id);
}, [id]);
const handleInputChange = event => {
const {name, value} = event.target;
setCurrentTask({...currentTask, [name]: value});
};
const setActiveMember = (member, index) => {
setCurrentMember(member);
setCurrentIndex(index);
};
const retrieveMembers = () => {
UserDataService.listMembers()
.then(response => {
setMembers(response.data);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
const updateCompleted = status => {
var data = {
id: currentTask.id,
userId: currentTask.userId,
title: currentTask.title,
ownerId: currentMember.userId,
ownerName: currentMember.firstName,
description: currentTask.description,
completed: status,
dueDate: currentTask.dueDate
};
TaskDataService.update(currentTask.id, data)
.then(response => {
setCurrentTask({...currentTask, completed: status});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
const updateTask = () => {
TaskDataService.update(currentTask.id, currentTask)
.then(response => {
console.log(response.data);
setMessage("The task was updated successfully!");
})
.catch(e => {
console.log(e);
});
};
const deleteTask = () => {
TaskDataService.remove(currentTask.id)
.then(response => {
console.log(response.data);
navigate("/tasks");
})
.catch(e => {
console.log(e);
});
};
return (
<PageLayout>
<FadeIn>
<div className="list row">
<div className="col-md-6">
{currentTask ? (
<div className="edit-form">
<h4>Task</h4>
<form>
<div className="form-group">
<label htmlFor="title" class="form-label">Title</label>
<input
type="text"
className="form-control"
id="title"
name="title"
value={currentTask.title}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label htmlFor="description" class="form-label">Description</label>
<input
type="text"
className="form-control"
id="description"
name="description"
value={currentTask.description}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label htmlFor="dueDate" class="form-label">Due Date</label>
<DatePicker
onChange={date => handleInputChange({
target: {
value: date.toISOString().split("T")[0],
name: 'dueDate'
}
})}
name="dueDate"
dateFormat="yyyy-MM-dd"
value={currentTask.dueDate.toString().split("T")[0]}
/>
</div>
<div className="form-group">
<label htmlFor="status" className="form-label">
<strong>Status:</strong>
</label>
{currentTask.completed ? " Done" : " Not Done"}
</div>
<ul className="list-group">
<label htmlFor="owner" className="form-label">Task Owner</label>
{members &&
members.map((member, index) => (
<li
className={
"list-group-item " + (index === currentIndex ? "active" : "")
}
onClick={=> setActiveMember(member, index)}
key={index}
>
{member.firstName} {member.lastName}
</li>
))}
</ul>
<div className="col-md-6">
{currentMember ? (
<div>
</div>
) : (
<div>
</div>
)}
</div>
</form>
{currentTask.completed ? (
<button
className="badge text-bg-warning mr-4"
onClick={() => updateCompleted(false)}
>
Not Done?
</button>
) : (
<button
className="badge text-bg-primary mr-2"
onClick={() => updateCompleted(true)}
>
Done!
</button>
)}
<button className="badge text-bg-danger mr-2" onClick={deleteTask}>
Delete
</button>
<button
type="submit"
className="badge text-bg-success"
onClick={updateTask}
>
Update
</button>
<p>{message}</p>
</div>
) : (
<div>
<br/>
<p>Please click on a Task...</p>
</div>
)}
</div>
</div>
</FadeIn>
</PageLayout>
);
};
My problem is with the member selection piece, where you can change ownership of the task:
<ul className="list-group">
<label htmlFor="owner" className="form-label">Task Owner</label>
{members &&
members.map((member, index) => (
<li
className={
"list-group-item " + (index === currentIndex ? "active" : "")
}
onClick={=> setActiveMember(member, index)}
key={index}
>
{member.firstName} {member.lastName}
</li>
))}
</ul>
...and the function where we actually update the task:
const updateTask = () => {
TaskDataService.update(currentTask.id, currentTask)
.then(response => {
console.log(response.data);
setMessage("The task was updated successfully!");
})
.catch(e => {
console.log(e);
});
};
Selecting a new owner from the list did not actually change the ownerId/ownerName value in the task. I have figured out that this is because the new owner values live in currentMember, while the task information lives in currentTask - so I need to figure out how to get information from the updated currentMember into the proper fields in currentTask. I've monkeyed around with configurations but can't find a way to do this. Any advice?
I figured it out. The key was to define the task structure in a variable so I could specify from where the ownerId value would be pulled.
Original:
const updateTask = () => {
TaskDataService.update(currentTask.id, currentTask)
.then(response => {
console.log(response.data);
setMessage("The task was updated successfully!");
})
.catch(e => {
console.log(e);
});
};
Updated:
const updateTask = () => {
var data = {
id: currentTask.id,
userId: currentTask.userId,
title: currentTask.title,
ownerId: currentMember.userId,
ownerName: currentMember.firstName,
description: currentTask.description,
dueDate: currentTask.dueDate
};
TaskDataService.update(currentTask.id, data)
.then(response => {
console.log(response.data);
setMessage("The task was updated successfully!");
})
.catch(e => {
console.log(e);
});
};

Marker won't display on the map. What can I do to solve this?

What should I do? I've tried to create a custom Marker using an icon but it won't display on the map. Also, I tried to put a simple Marker but again...no results.
In the last part, I was trying to display just 3 Markers on the map.
import React, {Component} from "react";
import GoogleMapReact from 'google-map-react';
import './LocMap.css'
import axios from "axios";
import "./marker.png"
const Marker = (e) => {
if (e.length > 0) {
return (
<div>
<img src={require("./marker.png")} alt={"marker"}/>
</div>
);
} else
return (<div/>);
};
const props = {
center: {
lat: 46.150835,
lng: 24.333331,
},
zoom: 14
};
class LocMap extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
startDate: "",
endDate: "",
locations: [],
};
}
componentDidMount() {
axios.get('http://localhost:8080/users/getAll')
.then(res => {
const data = res.data
console.log(data)
this.setState({users: data})
})
.catch(
err => {
console.log(err)
}
)
}
handleSubmit = async e => {
e.preventDefault();
const data = {
startDate: this.startDate,
endDate: this.endDate
}
console.log(data.startDate)
console.log(data.endDate)
await axios.get("http://localhost:8080" + "/location/filter?startDate=" + data.startDate + "&endDate=" + data.endDate)
.then(
res => {
const data = res.data
this.setState({locations: data})
})
.catch(
err => {
console.log(err)
}
)
};
render() {
return (
<div className='Location'>
<div className="container">
<div className="select-container">
<select>
{
this.state.users.map(item => (
<option key={item.id} value={item.email}>
{item.email}
</option>
))
}
</select>
</div>
</div>
<form onSubmit={this.handleSubmit}>
<div id='startdate'>
<label>Start date</label>
<input type="text" format="MM/DD/YYYY" name='startDate' id='a1' placeholder="Start Date"
onChange={e => this.startDate = e.target.value}/>
</div>
<div id='enddate'>
<label>End date</label>
<input type="text" format="MM/DD/YYYY" name='endDate' id='a2' placeholder="End Date"
onChange={e => this.endDate = e.target.value}/>
</div>
<button type="submit" name="search" id="search">Search</button>
</form>
<div id="mapArea">
<GoogleMapReact
defaultCenter={props.center}
defaultZoom={props.zoom}
>
{this.state.locations.length > 0 ? (
<Marker
lat={this.state.locations[0].latitude}
lng={this.state.locations[0].longitude}
/>
) : (
<Marker
lat={0}
lng={0}
/>
)}
{this.state.locations.length > 1 ? (
<Marker
lat={this.state.locations[1].latitude}
lng={this.state.locations[1].longitude}
/>
) : (
<Marker/>
)}
{this.state.locations.length > 2 ? (
<Marker
lat={this.state.locations.latitude}
lng={this.state.locations.longitude}
/>
) : (
<Marker/>
)}
</GoogleMapReact>
</div>
</div>
);
}
}
export default LocMap;
I apologize for incorectly formatting the code. It's my first post here.

formik not changing value of input on event

I cant seem to figure this out.
I have a form that is validated by formik.
When i click the search button, I make an api call to get a list of addresses.
When i click on one of those addresses, I want to populate the inputs with the data from the clicked address.
I can do all of the above except for the last part. I have tried using document.getElementById.innerHTML, setting it in state and having the input controlled by that state object, but i cant seem to get it to populate with any data.
import React, { Component } from 'react';
import { Formik, Form, Field } from 'formik';
import Axios from 'axios';
class CharityAgreement extends Component {
constructor(props) {
super(props);
this.state = {
response: [],
visible: true,
address: ''
}
}
postcodeChange = (e) => {
console.log(e.target.value);
this.setState({ 'postcode': e.target.value })
}
searchPostcode = () => {
this.setState({ 'visible': true });
if (this.state.postcode.length > 0) {
Axios.get('post code api lookup here')
.then(response => {
this.setState({ 'response': response.data });
console.log('response data:', response.data);
})
} else {
}
}
addressClick = (e) => {
console.log('CLICKED ADDRESS:', e.target.innerHTML);
this.setState({ 'visible': false });
}
render() {
const result = this.state.response.map((item) => {
if (this.state.visible === true) {
return (
<p key={item.Id} onClick={this.addressClick} className='lookup-data'>{item.address1Field} {item.address2Field} {item.townField} {item.countyField}</p>
)
} else {
}
})
return (
<div className='body-wrapper'>
<main id='mainContent' className='container'>
<div className='page-content-wrapper'>
<div className='raised-bordered-wrapper'>
<div className='raised-bordered quiz form'>
<Formik
initialValues={{
address_1:'',
}}
validate={(values) => {
const errors = {};
if (!values.address_1) errors.address_1 = 'Required';
return errors;
}}
onSubmit={this.handleSubmit}
render={({
touched,
errors,
values,
handleChange,
handleBlur,
handleSubmit
}) => (
<Form>
<div className='form-row'>
<span className='form-cell-wrapper'>
<label>Postcode Lookup</label>
<Field
name='postcode-lookup'
type='text'
onChange={this.postcodeChange}
/>
<button className='btn' onClick={this.searchPostcode}>Search</button>
</span>
</div>
{result}
<div className='form-row'>
<h5>License Code and Area</h5>
<span className='form-cell-wrapper'>
<label>Address Line 1</label>
<Field
name='address_1'
value={values.address_1}
onChange={handleChange}
id='address'
type='text'
style={{
borderColor:
errors.address_1 && touched.address_1 && "tomato"
}}
/>
</span>
</div>
</Form>
)}
/>
</div>
</div>
</div>
</main>
</div>
)
}
}
export default CharityAgreement;

How to call a method after successfully storing data in state in react

import React from 'react';
import fetch from 'isomorphic-fetch';
import { lookup } from 'dns';
export default class Pagination extends React.Component {
constructor(props){
super(props);
this.state = {
contacts : [],
per:5,
page:1,
totalPages:null,
country:null
}
}
componentDidMount(){
document.getElementById('us').click()
}
handleCountry = (country=null) => {
const {per, page, contacts} = this.state;
if (country === null){
country = 'United States'
}
const url = `http://127.0.0.1:8000/api/users/?limit=${per}&page=${page}&country=${country}`
fetch(url)
.then(response => response.json())
.then(
json => {
this.setState({
contacts:json.data
})
}
)
}
loadMore = (country) => {
this.setState(prevState => ({
page: prevState.page + 1,
}), this.loadContacts(country))
}
handleCountry = (event) => {
this.setState({
country:event.target.value,
page:1
})
this.loadContacts(event.target.value);
}
render(){
return (
<div>
<div>
<label class="radio-inline">
<input type="radio" id="us" name="country" value="United States" onClick={this.handleCountry} />United States
</label>
<label class="radio-inline">
<input type="radio" id="india" name="country" value="India" onClick={this.handleCountry} />India
</label>
<label class="radio-inline">
<input type="radio" id="canada" name="country" value="Canada" onClick={this.handleCountry} />Canada
</label>
</div>
<ul className="contacts" style={{ width:'300px' }}>
{
this.state.contacts.map(contact =>
<li key={contact.id} style={{ padding:'5px 5px 5px 5px' }}>
<div className="contact" style={{ background:'#0099ff', padding:'10px', color:'white' }}>
<div>{ contact.id }</div>
<div>{ contact.country }</div>
<div>{ contact.name }</div>
</div>
</li>
)
}
</ul>
<button onClick={() => this.loadMore(this.state.country)}>Load More</button>
</div>
)
}
}
Here I am stuck with a issue in reactjs.
When i am clicking any radio button its calling handleCountry() method and passing event.
Then i am storing the event in state. Then calling handleCountry() function to fetch api.
But in handleCountry() method first loadContacts() method calling then it storing the data in state.
So I am not getting correct result.
I can i make call loadContacts() after successfully storing data in state inside loadContacts() method.
Please have a look.
Use callback method with setState to achieve the expected result, it will be executed after successful state update.
Like this:
handleCountry = (event) => {
let { value } = event.target;
this.setState({
country: value,
page:1
}, () => {
this.loadContacts(value);
})
}
Check React Doc for more detail about setState.

React passing props from children to parent issue

I am a beginner in React. When I try to pass props from children to parent, the whole app is refreshed and the state recovery to initial. Is there any problem on my code? I have no idea how to solve it.
(ps: The following sentence is just for the number of words. Please don't see it. Why I have to add more details. If I have the ability to know every detail, I already solved it by myself)
Parent:
class App extends Component {
constructor(props) {
super(props);
this.state = {
stops: [],
legs: [],
driver: null,
finishedSign: false,
stopsSign: false,
legsSign: false,
driverSign: false
};
}
componentDidMount() {
console.log("-DID");
this.getStops();
this.getLegs();
this.getDriver();
}
// garentee all of data have received
checkFinished() {
const { stopsSign, legsSign, driverSign } = this.state;
const mark = stopsSign && legsSign && driverSign;
if (mark)
this.setState({
finishedSign: mark
});
}
// GET/STOPS API
getStops() {
fetch("/api/stops")
.then(res => res.json())
.then(stops => {
this.setState({ stops: stops, stopsSign: true }, () =>
console.log("stops fetched !", stops)
);
this.checkFinished();
});
}
// GET/LEGS API
getLegs() {
fetch("/api/legs")
.then(res => res.json())
.then(legs => {
this.setState({ legs: legs, legsSign: true }, () =>
console.log("driver fetched !", legs)
);
this.checkFinished();
});
}
// GET/Driver API
getDriver() {
console.log("-DRIVER");
fetch("/api/driver")
.then(res => {
return res.json();
})
.then(driver => {
this.setState(
{
driver: driver,
driverSign: true
},
() => console.log("driver fetched !", driver)
);
this.checkFinished();
});
}
// passing func
updateDriver(driver) {
console.log("update app!");
alert(driver);
}
renderMaps() {
return (
<Maps
stops={this.state.stops}
legs={this.state.legs}
driver={this.state.driver}
/>
);
}
renderDriverController() {
return (
<DiverController
legs={this.state.legs}
driver={this.state.driver}
update={this.updateDriver}
/>
);
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-3 col-md-3">
{this.state.finishedSign && this.renderDriverController()}
</div>
<div className="col-sm-8 col-md-8">
{
//this.state.finishedSign && this.renderMaps()
}
</div>
</div>
</div>
);
}
}
export default App;
children:
class DriverController extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.legs,
driver: this.props.driver
};
}
handleUpdate = e => {
const driver = null;
driver.activeLegID = this.refs.selectedLeg.value;
driver.legProgress = this.refs.selectedProgress.value;
if (driver.legProgress >= 0 && driver.legProgress <= 100)
this.props.update("test");
else alert("out of range!");
};
render() {
const { items, driver } = this.state;
console.log("items:", items);
return (
<form>
<hr />
<label>Driver Location:</label>
<div className="form-group">
<select
id="inputState"
className="form-control"
defaultValue={driver.activeLegID}
ref="selectedLeg"
>
{items.map(item => (
<option key={item.legID}>{item.legID}</option>
))}
</select>
<div className="input-group input-group-sm mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">
Percentage:
</span>
</div>
<input
type="number"
className="form-control"
defaultValue={driver.legProgress}
ref="selectedProgress"
/>
</div>
<button onClick={this.handleUpdate} className="btn btn-primary">
Submit
</button>
<hr />
</div>
</form>
);
}
}
export default DriverController;
Try to use
onClick={() => this.handleUpdate}
You should not pass props from a child to its parent. Thats an anti-pattern.
You could pass a function from parent to child which will be triggered in
the child and hence updating the required state in the parent.
Refresh issue:
I think cause the child is wrapped inside a form.
Add
e.preventDefault() to your handleSubmit function to prevent the refresh
handleUpdate = e => {
e.preventDefault()

Resources