I have set React state to data from an API
this.setState({loan: response.data})
response.data is a nested object
{
application: {
amount: 20,
interest: 10,
guarantor: {
firstName: "John",
lastName: "Doe"
}
},
userId: "123"
}
Normally inside the render function i can access
<p>{this.state.loan.userId}</p>
<p>{this.state.loan.application.amount}</p>
<p>{this.state.loan.application.guarantor.firstName}</p>
Now I can only access the first child of the loan. Except i practically set the state for each individual item in the object. Note console.log(this.state.loan.application.guarantor) works fine.
This is the API call
fetch(`http://localhost:8000/api/v1/loans/${this.state.id}`)
.then(res => {
return res.json();
}).then(response => {
this.setState({loan: response.data});
})
.catch(err => console.log(err));
const {loan} = this.state;
<div className="col-md-4">
<h5 className="title">Full Name</h5>
<p>{loan.fullName}</p>
<h5 className="title mt-3">Account Number</h5>
<p>{loan.accountNumber}</p>
<h5 className="title mt-3">Phone Number</h5>
<p>Phone Number</p>
</div>
<div className="col-md-4">
<h5 className="title">Loan Amount</h5>
<p>
{(loan.application.amount).toLocaleString("en-NG", {
style: "currency",
currency: "NGN"
})}
</p>
<h5 className="title mt-3">Interest Rate</h5>
<p>{loan.interestRate}%</p>
<h5 className="title mt-3">Duration</h5>
<p>{loan.duration} Months</p>
</div>
The response from API call
{
"application": {
"guarantor1": {
"fullName": "Ayebakuro Ombu",
"residentialAddress": "30 Udengs Eradiri Avenue Off Azikoro Village Road",
"occupation": "Accountant",
"netIncome": "50000",
"placeOfWork": "Dreamworld",
"employer": "Ayebakuro Ombu",
"relationship": "Employer",
"bvn": "0101010101",
"bank": "GTBank",
"accountNumber": "10101010101",
"phoneNumber": "010101010101"
},
"guarantor2": {
"fullName": "Ayebakuro Ombu",
"residentialAddress": "B48 Copa Cobana Estate, Wumba, Lokogoma",
"occupation": "business man",
"netIncome": "500000",
"placeOfWork": "Dreamworld",
"employer": "SafeScrow Tech",
"relationship": "Employer",
"bvn": "0101010101",
"bank": "GTBank",
"accountNumber": "0101010101",
"phoneNumber": "0101010101"
},
"mode": {
"name": "DreamWorld Savings And Loans Ltd",
"address": "30 Udengs Eradiri Avenue Off Azikoro Village Road",
"netIncome": "50000"
},
"bankDetails": {
"bank": "Parallex Bank",
"accountNumber": "0101010101",
"bvn": "0101010101"
},
"amount": 200000,
"number": "25642",
"date": "2019-03-22T02:37:58.069Z",
"purpose": "For debt payment"
},
"approval": {
"amount": 0,
"status": "Pending"
},
"issue": {
"status": false
},
"payment": {
"schedule": [],
"completed": false
},
"_id": "5c944a86abf7ea09c40301e5",
"accountNumber": "1000000002",
"fullName": "Ayebakuro Ombu",
"type": "Business",
"duration": 5,
"interestRate": 10,
"__v": 0
}
The error: LoanPage.js:61 Uncaught TypeError: Cannot read property 'amount' of undefined
at LoanPage.render (LoanPage.js:61)
Logging this.state.loan.application.amount logs correctly
When a component is rendered (like the following code), React calls the render method of corresponding component immediately.
ReactDom.render(<LoanPage />, element);
Event if you were to execute a asynchronous fetch in constructor, or componentWillMount method, that wouldn't prevent the React system from executing render.
This is how you should approach this problem. In constructor / componentWillMount, you should set this.state.loading = true, and then fire the fetch call. In the .then part of fetch call, setState to clear the loading flag like this:
this.setState({
loading: false,
loan: response.data
});
The render method of LoanPage can now benefit from the knowledge of 'fetch call in progress' like this:
render() {
if(this.state.loading) {
return (<h3>Loading...</h3>);
}
return (
<div> Loan amount is {this.state.loan.application.amount} </div>
);
}
You can change the first part of render (in if condition) to display a spinner or some equivalent. You should change the second part to render everything that you are rendering now.
Related
This is the set up:
In App.js i have routes for each step. i have a main object in which im updating values as the steps progress using props.
Here is my object:
const [postData, setPostData2] = useState({
'meta': {
"originally_created": todaysDate,
"user_agent": navigator.userAgent,
"ip_address": ip,
"tcpa_compliant": true,
"tcpa_consent_text": "By clicking Get My Free Quote below, I am agreeing to receive text messages from InsurTech Groups and business partners. I provide my signature expressly consenting to recurring contact from InsurTech Groups or its business partners at the number I provided regarding products or services via live, automated or prerecorded telephone call, text message, or email. I understand that my telephone company may impose charges on me for these contacts, and I am not required to enter into this agreement as a condition of purchasing property, goods, or services. I understand that I can revoke this consent at any time. Terms & conditions & Privacy policy apply.",
"landing_page_url": ""
},
"contact": {
"first_name": "",
"last_name": "",
"email": "",
"phone": "",
"address": "",
"city": "",
"state": "",
"zip_code": "",
"ip_address": ip,
},
"data": {
"drivers": [
{
"first_name": "",
"last_name": "",
"birth_date": "",
"gender": "",
},
{
"first_name": "",
"last_name": "",
"birth_date": "",
"gender": "",
}
],
"vehicles": [{
"year": "",
"make": "",
"model": "",
},
{
"year": "",
"make": "",
"model": "",
}
],
"requested_policy": {
"coverage_type": "",
},
"current_policy": {
"insurance_company": "",
}
}
});
Here is some logic so i can update the object with props:
useEffect(() => {
const stringifiedData = sessionStorage.getItem('main-form-data')
if (stringifiedData) {
const jsonData = JSON.parse(stringifiedData);
setPostData(jsonData);
}
}, []);
useEffect(() => {
sessionStorage.setItem('main-form-data', JSON.stringify(postData));
}, [JSON.stringify(postData)]);
const setPostData = (obj) => {
console.log('in app state', obj);
setPostData2(obj)
}
const setPostDataForPage = (data) => {
setPostData({ ...postData, ...data });
}
and this is how im passing it into each route:
<Route
path="/car-year"
element={<CarYear setPostData={setPostDataForPage} />}
/>
<Route
path="/car-make"
element={<CarMake setPostData={setPostDataForPage} />}
/>
<Route
path="/car-model"
element={<CarModel setPostData={setPostDataForPage} />}
/>
So here is the issue:
As i updated Car Year, Car Make & Car Model it updates in the console log as it supposed to but everytime it console log it completely overwrites the prior object.
Example:
YEAR STEP:
....rest of console log from object
vehicels: year: 1991
MAKE STEP:
....rest of console log from object
vehicels: make: Honda
as you can see it just over writes the year and replaces it with just the make
Here is how im updating in each step:
CAR YEAR PROPS UPDATE:
props.setPostData({
vehicles:
{
year: year,
}
})
CAR MAKE PROPS UPDATE:
props.setPostData({
vehicles: [
{
make: make,
}
]
})
Why does it do this and how can i fix it?
Here is a *bonus question lol, if i have two vehicles how can i make sure that when the person enters a second vehicle it doenst overwrite the first vehicle data!
I know this one is super long and thank you in advance!!!! <3
You're spreading the original state in the setter. React won't mutate the state synchronously, so you end calling all 3 setters on the original state.
To use the actual state, pass a callback to the state setter with the previous state as the parameter.
const setPostDataForPage = (data) => {
setPostData(prevData => { ...prevData, ...data });
}
I have API that stores JSON data as shown in JSON body below... I wanted to show the data amount stored in installments but it didn't work good because its showing me each amount value two times and I couldn't figure out the problem here.
{
"response": [{
"floors": [{
"flats": [{
"status": "sold",
"price": "150000",
"currency": "USD",
"end_date": "Not Set",
"buyer": "ella",
"buyer_phone_number": "002822128",
"receipt_number_field": "553108012022",
"size_unit": "M",
"_id": "61d9b61397e87e39832a5abb",
"flat_number": 1,
"description": "This is a newly created flat.",
"city": "NY",
"payment": {
"installment_payment": {
"installments": [{
"amount": "1344",
"date": "2022-01-13",
"is_paid": false
},
{
"amount": "444",
"date": "2022-01-24",
"is_paid": false
},
{
"amount": "44444",
"date": "2022-01-17",
"is_paid": false
}
],
"remaining": "150000"
},
"paid_amount": "1234"
},
"floor": "61d9b61397e87e39832a5aba",
"building": "61d9b61397e87e39832a5ab9",
"size": "176.25",
"directions": " south",
"createdAt": "2022-01-08T16:04:43.557Z",
"updatedAt": "2022-01-08T16:22:29.220Z",
"__v": 0
},
my code:
<div v-for="(flat,index) in Flats" :key="index">
<div v-for="(find,indexT) in flat.payment" :key="indexT" >
<div v-if="flat.payment.installment_payment">
<div v-for="(find,indexT) in flat.payment.installment_payment.installments" :key="indexT">
<div v-if="find.amount >0">
<p> {{find.amount}}$ amount </p>
</div>
</div>
</div>
</div>
</div>
p.S: I stored my API data in array Flats
This will probably work, but it's untested.
You generally do not want to use v-if inside of v-for; instead, you should filter the data first and use the result in the v-for loop. [reference]
Also, since each flat has an _id field, you can use that instead of the index for the top level :key attribute.
<div v-for="flat in flatsWithPayments" :key="flat._id">
<div v-for="(installment, index) in getInstallmentsWithPaymentGTZero(flat.payment.installment_payment.installments)" :key="index">
<p> {{installment.amount}}$ amount </p>
</div>
</div>
Obviously, replace Flats with your data, but also note that in order to compare the payment amount, it needs to be converted with either Number(), parseInt() or parseFloat()
// Flats = { ... }
export default {
computed: {
flatsWithPayments() {
return Flats.filter(f => f.payment != undefined)
}
},
methods: {
getInstallmentsWithPaymentGTZero(installments) {
return installments.filter(i => Number(i.amount) > 0)
}
}
}
Live CodeSandbox link.
I'm trying to access and pull in data from an API, specifically the price text value below:
"price": {
"currency": "CAD",
"text": "500"
},
JS code (everything else pulls in fine, just the <p>${product.price.text}</p> I'm having trouble with):
// Fetch Data
async function getData() {
const res = await fetch(url);
const data = await res.json();
let output = "";
// Loop through first 'groups' array
data.groups.map(function (group) {
// Loop through each 'equipments' array
group.equipments.map((product) => {
// Define below variable to match cat products only
const catProducts =
product["dealer-name"] === "CATERPILLAR FINANCIAL SERVICES CORPORATION";
// If the dealer name is everything but cat products (aka only battlefield products)..
if (!catProducts) {
// Loop through each 'photos' array
product.photos.map(() => {
// Then output the data
// If year is undefined, replace with empty string
output += `
<div class="card">
<img class="img-fluid" src=${product.photos[0].text} alt=${
product.model
} />
<div class="card--body">
<h3>${product.year ?? ""} ${product.manufacturer} ${
product.model ?? ""
}</h3>
<p>${product.city ?? "City Not Available"}, ${product.state}</p>
<p>${product.hours} hours</p>
<p>${product.price.text}</p> <--- Not working
<a href='https://used.ca/en/${product["group-code"]}/${
product["serial-number"]
}' class="btn btn-primary">View Details</a>
</div>
</div>
`;
});
}
});
});
// Add to slider
$(".used-slider").slick("slickAdd", output);
}
getData();
Currently throwing a console error: "app.js:26 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'text')"
API structure:
{
"version": "5",
"company-code": "N001",
"language-code": "en-CA",
"total-count": 148,
"created-date": "2021-09-22T18:12:03.2387128+00:00",
"template-identifier": "4da31196-7f4b-4529-b832-90d40ef4a024",
"group-type-code": "ProductFamilyCategory",
"groups": [
{
"group-code": "Backhoe Loaders - Chargeuses-pelleteuses",
"group-name": "Backhoe Loaders",
"group-display-name": "Backhoe Loaders",
"count": 7,
"equipments": [
{
"id": "4536522",
"dealer-name": "DEALER NAME",
"GroupName": "Compact Track Loader",
"product-family-code": "CTL",
"product-family": "COMPACT TRACK LOADER",
"product-family-display-name": "Compact Track Loader",
"manufacturer-code": "CAT",
"manufacturer": "MANUFACTURER",
"model": "RUBBER TRACKS FOR CTL 259D ",
"serial-number": "XXXXX",
"year": "2016",
"hours": 0,
"city": "Ville St-laurent, Montréal",
"state": "QC",
"certification": "None",
"availability": "Available",
"price": {
"currency": "CAD",
"text": "500"
},
"product-family-categories": {},
"photos": [
{
"text": "https://s7d2.scene7.com/is/image/CatUsedProduction/wtk?JHNyYz04ZjRjN2UyYzJkMzFmZWNjY2NiZDQ1MTc2NTA4MGY3MiYkdHh0PUJBVFRMRUZJRUxEJTIwRVFVSVBNRU5UJTIwUkVOVEFMUyUyMCUyOFFVJUMzJTg5QkVDJTI5JjUxMTY2"
}
]
}
]
}
]
}
Anyone know why I'm unable to access the price text value but can access all the others?
The error implies that some products don't have a price property. You need to check for this before trying to access the text property. You can display a default placeholder instead.
You can use optional chaining to simplify this.
<p>${product.price?.text || "unknown"}</p> <--- Not working
I fetch an api on componentDIdMount() then store the json to a state then I pass that state as a prop, I have no problem showing the data except on arrays.
<Component details={this.state.details} />
json:
{
"adult": false,
"backdrop_path": "/qonBhlm0UjuKX2sH7e73pnG0454.jpg",
"belongs_to_collection": null,
"budget": 90000000,
"genres": [
{
"id": 28,
"name": "Action"
},
{
"id": 878,
"name": "Science Fiction"
},
{
"id": 35,
"name": "Comedy"
},
{
"id": 10751,
"name": "Family"
}
]
}
then I try to map the genres:
<div className={style.genre}>
{details.genres.map(g => (
<span key={g.id}>{g.name}</span>
))}
</div>
But then I get Cannot read property 'map' of undefined, I don't know why this is happening because I'm able to do details.budget
It's trying to read data before you get the result from api.
so write the map function as
{details&&details.genres&&details.genres.map(g => (
<span key={g.id}>{g.name}</span>
))}
In react Initially when component is mounted, render() function is called and then componenentDidMount() is called in which you fetch data. So Initially details is empty. So you need to write the condition.
I have an object like:
export const contact = {
_id: "1",
first:"John",
name: {
first:"John",
last:"Doe"
},
phone:"555",
email:"john#gmail.com"
};
I am reading it like
return (
<div>
<h1>List of Contact</h1>
<h1>{this.props.contact._id}</h1>
</div>
)
in this scenario I am getting expected output.
return (
<div>
<h1>List of Contact</h1>
<h1>{this.props.contact.name.first}</h1>
</div>
)
But when I read the nested property I am getting error like
Uncaught TypeError: Cannot read property 'first' of undefined
How to read these king of nested objects in react? Here is my source
Three things you need to address here:
this is your contacts-data and i don't see any first property within the name object:
export const contacts = {
"name": "mkyong",
"age": 30,
"address": {
"streetAddress": "88 8nd Street",
"city": "New York"
},
"phoneNumber": [{
"type": "home",
"number": "111 111-1111"
}, {
"type": "fax",
"number": "222 222-2222"
}]
};
You are calling this.props.fetchContacts(); on componentDidMount, hence in the first render call the state is still empty, the action call and the reducer will pass new props or change the state then you get to the second render call and that's the moment you have the data ready for use.
So you should check for the existing of the data before you try to use it. one way is to just conditionally render it (of course there are better ways to do it, this is just to make a point):
render() {
const { contacts } = this.props;
return (
<div>
<h1>List of Contacts</h1>
<h2>{contacts && contacts.name && contacts.name.first
//still getting error. "this.props.contacts.name" alone works
}</h2>
<h2>{contacts && contacts.address && contacts.address.city}</h2>
</div>
)
}
You are trying to use this.props.contact.name.first is that a typo? shouldnt it be contacts instead of contact?
EDIT:
As a followup for your comment, as a general rule in JavaScript (or any other language for that manner) you should always check the existence reference of an object before you are trying to access it's properties.
As for your use case you can use defaultProps if you must have a value to render or you can even simplify the scheme of your data.
This is much simpler to manage:
export const contacts =
{
"fName": "mkyong",
"lName": "lasty",
"age": 30,
"streetAddress": "88 8nd Street",
"city": "New York",
"homeNumber": "111 111-1111",
"faxNumber": "222 222-2222"
};
Than this:
export const contacts =
{"name": "mkyong",
"age": 30,
"address": {
"streetAddress": "88 8nd Street",
"city": "New York"
},
"phoneNumber": [
{
"type": "home",
"number": "111 111-1111"
},
{
"type": "fax",
"number": "222 222-2222"
}
]
};