Attempting to map within React returning undefined - reactjs

I have the following Mongoose schema:
const SubmitDebtSchema = new Schema ({
firebaseId: String,
balance: [{
balanceDate: Date,
newBalance: Number
}]
});
This database schema is called in my parent component using the useEffect hook, and passed down as props to my child component.
const fetchDebts = debts.map (debt => {
return (
<IndividualDebtCard key={debt._id}
transactions={debt} />
)
})
I then store the prop in my child component as a variable, and use another useEffect to console the result of this variable upon rendering:
const debts = props.transactions
useEffect(() => {
console.log(debts)
}, [debts])
For reference, this is what an example console log would look like:
balance: Array (2)
0 {_id: "5fea07cd143fd50008ae1ab2", newBalance: 1500, balanceDate: "2020-12-28T16:29:00.391Z"}
1 {_id: "5fea0837b2a0530009f3886f", newBalance: 1115, balanceDate: "2020-12-28T16:30:45.217Z"}
What I then want to do, is map through this variable, pick out each 'newBalance', and 'balanceDate' and render them on my page.
However, I'm getting an undefined error every time I try to load my component...
This is what I've tried so far:
{ debts.map(debt => {
return (
<div className="transaction-history">
<div className="transaction-history-entry">
<p>{debt.balance.balanceDate}</p>
<p>-£{debt.balance.newBalance}</p>
</div>
</div>
)
})}
Can anyone point out where I'm going wrong? I know it'll be something obvious, but can't figure it out.
EDIT: I think the undefined is coming from how I'm attempting to call my 'balanceDate' and 'newBalance' - if I console log what I'm trying to map it's returning undefined.

You need to check for debts to have value. try this:
{
debts.balance && !!debts.balance.length && debts.balance.map((debt, index) => {
return (
<div key={index} className="transaction-history">
<div className="transaction-history-entry">
<p>{debt.balance.balanceDate}</p>
<p>-£{debt.balance.newBalance}</p>
</div>
</div>
);
});
}

Related

How can I handle the value of array or data in json file?

I'm making checkbox function using data in json file.
I want to show 'O' and 'X' according to the value called checked in json file.
And want checked value changed when 'O' or 'X' is clicked.
For this, I tried to make handleCheck function but it doesn't work.
May I know which part is wrong and how to modify it?
App.js
import "./styles.css";
import dummy from "./database/data.json";
export default function App() {
const handleCheck = (number) => {
dummy.state[number].checked !== dummy.state[number].checked;
};
return (
<div className="App">
{dummy.state.map((item, idx) => {
return (
<div
onClick={() => {
handleCheck(item.id);
}}
key={idx}
>
{item.checked ? "O" : "X"}
</div>
);
})}
</div>
);
}
data.json
{
"state": [
{
"id": "1",
"checked": true
},
{
"id": "2",
"checked": true
},
{
"id": "3",
"checked": true
}
]
}
codesandbox
https://codesandbox.io/s/serene-forest-j0cb25?file=/src/database/data.json:0-176
You're running into some JavaScript syntax issues and React-specific issues.
One of them is that you're modifying the JSON file array directly, and React isn't going to re-render on those changes. A way to fix this would be to use the useState hook with your dummy data as the initial state. You can accomplish this like so:
const [data, setData] = useState(dummy.state)
Now, we have a data variable which corresponds to your dummy data's state array and a setData function which will let us set data to whatever value we'd like and have React re-render the UI.
The next bit we need to tackle will be to make sure the handleCheck function is updating our useState's data variable and setting it correctly. We have two big issues right now with this function.
First, you're really close to flipping the checked value, but the o.checked !== o.checked is doing a comparison instead of setting your variable; basically, the code is asking "is X strictly not equal to X?" MDN has a great page on what strict inequality is. What we'd like to do is set A.checked to "not A.checked". To fix this, we can do item.checked = !item.checked.
Additionally, calling dummy.state[number].checked doesn't point to what you probably think it will since your IDs are keys on an object. If number = 1, then dummy.state[1] actually ends up being the second item in the array, because JavaScript arrays are 0-indexed. Instead, we can update the function like so
const handleCheck = (id) => {
setData(
data.map((item) => {
return item.id === id
? { ...item, checked: !item.checked }
: { ...item };
})
);
};
Now what we're doing is using the map function to loop over your data, destructure it (arrays are references, and we don't want to mutate the "old" data), and conditionally change the checked property based on if the item's ID matches the ID passed into handleChecked.
I've forked and updated your CodeSandbox so you can see it all put together in App.js.
First you need to move the json data to a state value and update the state value accordingly and at the end you can push the json data out
import "./styles.css";
import dummy from "./database/data.json";
export default function App() {
const [data,setData]=useState(dummy.state)
const handleCheck = (number) => {
setData(state=>{
let newState=[...state]
newState[number].checked = !state[number].checked;
return newState
}
};
return (
<div className="App">
{data.map((item, idx) => {
return (
<div
onClick={() => {
handleCheck(item.id);
}}
key={idx}
>
{item.checked ? "O" : "X"}
</div>
);
})}
</div>
);
}
The data state value have the correct values and you can use them accordingly

Unable to loop single react component

I'm trying to loop my StudentInfo component so a blurb of information can be repeated down an array of 25 objects provided through an API for each object/a student (name, email, company, etc.) I'm not
sure where I'm going wrong; here is my attempted loop via the map function:
export default function StudentList() {
let students = useState(null);
return (
<div className="StudentList">
<div className="row">
<div className="col">
{students.map(function (student, index) {
if (index <= 25) {
return (
<div className="col" key={index}>
<StudentInfo data={student} />
</div>
);
}
})}
</div>
</div>
</div>
);
}
Can someone see what I'm missing or what I might have skipped? I usually assume that I must be doing something wrong because I'm still new at React and trying to adapt other code I used for a weather forecast app to be used for this, but I don't think it's translating over well.
When I run this, I see the first object twice, i.e. Name, Email, Company, etc. it shows the info for the same person twice, rather than the first person, the second person, etc. I want to be able to see this pull all the objects from the array.
Here is the information I'm pulling to return as an entry on student info:
export default function StudentInfo() {
const [info, addInfo] = useState(" ");
function setInfo(response) {
addInfo({
number: response.data.students[0].id,
first: response.data.students[0].firstName,
last: response.data.students[0].lastName,
email: response.data.students[0].email,
company: response.data.students[0].company,
skill: response.data.students[0].skill,
average: response.data.students[0].grades[0],
});
}
let url = "https://api.hatchways.io/assessment/students";
axios.get(url).then(setInfo);
return (
<div className="StudentInfo">
<h1>{info.number}.</h1>
<h2>
Name: {info.first} {info.last}
</h2>
<h2>Email: {info.email}</h2>
<h2>Company: {info.company}</h2>
<h2>Skill: {info.skill}</h2>
<h2>Average: {info.average}</h2>
</div>
);
}
I'm using "if (index <= 25)" as there are 25 total entries that I want showing, but as I mentioned, I have no doubt I'm going about this incorrectly. I want this to loop through all 25 objects with the above information, as I keep saying. I'm sorry if I'm not speaking technically enough to be understood, as I am still learning.
I just want this to return 25 times with info from each object so that it's all listed out.
This is what it currently looks like
UPDATE
I've been tinkering and was able to repeat the entry, however I'm now having trouble getting the unique information, i.e. I'm only seeing the first entry over and over. I'm not sure how to reiterate with new information since it's only going to response.data.students[0]. This is what my code looks like now, where the mapping is:
export default function StudentList() {
let [loaded, setLoaded] = useState(false);
let [students, setStudent] = useState(" ");
function doInfo(response) {
console.log(response.data);
setStudent(response.data.students[0].id);
setLoaded(true);
}
if (loaded) {
return (
<div className="StudentList">
<div className="row">
<div className="col">
{students.map(function (id) {
return <StudentInfo data={id} />;
})}
</div>
</div>
</div>
);
} else {
let url = "https://api.hatchways.io/assessment/students";
axios.get(url).then(doInfo);
}
}
Can someone help me code it so it runs through all 25 entries, not just the first one?
There are some errors in your code.
UseState
React useState hook returns an array with a value and a setter method to update the state useState docs.
const [students, setStudents] = useState(null);
Iterate over null values
If your state starts with a null value you will not be able to iterate over it. To avoid getting an error you should make sure to use the map operator when your state has a value.
{students && students.map(function (student, index) {
...
})}
Handling side effects
You should move your API request (and set your info state) inside of a useEffect (useEffect docs). This way you will set those values asynchronously after the component is mounted.
export default function StudentInfo() {
const [info, addInfo] = useState(null);
useEffect(() => {
async function fetchInfo() {
const url = "https://api.hatchways.io/assessment/students"; //should be a constant outside the component
const response = await axios.get(url);
addInfo({
number: response.data.students[0].id,
first: response.data.students[0].firstName,
last: response.data.students[0].lastName,
email: response.data.students[0].email,
company: response.data.students[0].company,
skill: response.data.students[0].skill,
average: response.data.students[0].grades[0],
});
fetchInfo();
}, [])
return (
<div className="StudentInfo">
{info &&
<h1>{info.number}.</h1>
<h2>
Name: {info.first} {info.last}
</h2>
<h2>Email: {info.email}</h2>
<h2>Company: {info.company}</h2>
<h2>Skill: {info.skill}</h2>
<h2>Average: {info.average}</h2>
}
</div>
);
}
export default function StudentList() {
const [students, setStudents] = useState([]);
Check React's documentation React.
Also check if it cannot be declared that way, you need to use a ternary if. Check that part in React's documenation Conditional rending
{students.map(function (student, index) {
index <= 25 && (
<div className="col" key={index}>
<StudentInfo data={student} />
</div>
);

Problem occur when create a search function in React

I'm new to react, and I'm working on a small project that uses a search bar to find data that I've gotten from my database.
The code that I tried is below:
function Posts() {
const [notes, setNotes] = useState([]);
useEffect(()=>{
getAllNotes();
}, []);
const getAllNotes = async () => {
await axios.get(`/buyerPosts`)
.then ((response)=>{
const allNotes=response.data.existingPosts;
setNotes(allNotes);
})
.catch(error=>console.error(`Error: ${error}`));
}
console.log(notes);
const filterData = (postsPara, searchKey) => {
const result = postsPara.filter(
(notes) =>
notes?.address.toLowerCase().includes(searchKey) ||
notes?.contact.toString().toLowerCase().includes(searchKey)
);
setNotes(result);
};
const handleSearchArea = (e) => {
const searchKey = e.currentTarget.value;
axios.get(`/buyerPosts`).then((res) => {
if (res?.data?.success) {
filterData(res?.data?.existingPosts, searchKey);
}
});
};
return(
<div className="posts-b">
<div className="posts__container-b">
<div className="search-box">
<input type="text" placeholder="What are you looking for?" onChange={handleSearchArea}></input>
<i className="fas fa-search"></i>
</div>
<main className="grid-b">
{notes.map((note,index)=> (
<article>
<div className="text-b">
<h3>Post ID: {index + 1}</h3>
<p>Location: {note.address}</p>
<p>Post Type: {note.postType}</p>
<p>Address: {note.address}</p>
<p>Telephone No: {note.contact}</p>
</div>
</article>
))}
</main>
</div>
</div>
);
}
export default Posts;
From the first API call, I get a length 10 array of objects. This image shows the data that I got from the first API call.
There is another array of objects called as wasteItemList in all 10 array as in this picture. I created the search function correctly and it works to search the data in the above length 10 array of objects using this code notes?.address.toLowerCase().includes(searchKey) || notes?.contact.toString().toLowerCase().includes(searchKey). Then I try to modify above code to search the data inside the wasteItemList array like this notes?.wasteItemList?.item.toLowerCase().includes(searchKey) || notes?.wasteItemList?.wasteType.toLowerCase().includes(searchKey). But it does not work and get an error that says 'Unhandled Rejection (TypeError): Cannot read property 'toLowerCase' of undefined'.
What is the reason for this problem. Is this impossible to search the data in an inside array of objects that are already in another array of objects? If possible how can I solve this problem?
Any other comments on the code are also welcome. I'm here to learn.
Thank you!
notes?.address is single string attribute however, notes?.wasteItemList is list of objects. hence notes?.wasteItemList?.item will return an undefined
what you can do is run a map to extract a list of item key and join using join function and then use includes function,
the following snippets will get you the idea
notes?.wasteItemList?.map(wasteItem => wasteItem.item).join(' ').toLowerCase().includes(searchKey)
wasteItemList is an array, you are trying to access it with:
wasteItemList?.item.toLowerCase()
AND
wasteItemList?.wasteType.toLowerCase()
This will cause wasteType to be undefined, so toLowerCase() will also throw an error since it will be similar to that you are executing this:
undefined.toLowerCase() => Cannot read property 'toLowerCase' of undefined
For wasteItemList is an array so if you want to access its variable need to access it with another loop.
You're trying to call .toLowerCase() on an array. You will need to search within that array (it doesn't matter that it's nested inside an object in another array) - here's a suggestion for how to do that:
const result = postsPara.filter(notes =>
notes?.wasteItemList?.some(
wasteItem =>
wasteItem.item?.toLowerCase().includes(searchKey) ||
wasteItem.wasteType?.toLowerCase().includes(searchKey),
),
);
Since this is a general javascript question and not specific to react, you might want to change the tag.

Component gives type error if it includes image element

The Project
I have a project (React, Typescript, React useContext) that calls an api to fetch information about the episodes of a series, then display the information as cards.
Current status
Last time I ran the project, it worked, I deployed it to Heroku, it worked. One month later, after no changes, it doesn´t work either on my local or on heroku, they throw the same error.
Errors:
Uncaught TypeError: Cannot read property 'medium' of null
at EpisodesList.tsx:21
EpisodesList.tsx:21 Uncaught (in promise) TypeError: Cannot read property 'medium' of null
at EpisodesList.tsx:21
The Episodelists component
const EpisodesList = (props: any): JSX.Element => {
const { episodes, toggleFavAction, favourites, store } = props;
const { state, dispatch } = store;
return episodes.map((episode: Episode) => {
return (
<div key={episode.id} className="episode-box">
<section>
<img
src={episode.image.medium}
/>
<div>{episode.name}</div>
<div>
Season: {episode.season} Number: {episode.number}
</div>
</section>
</div>
);
});
};
export default EpisodesList;
The Home page that uses the component
const Home = () => {
const { state, dispatch } = React.useContext(Store);
useEffect(() => {
state.episodes.length === 0 && fetchDataAction(dispatch);
});
const props: EpisodeProps = {
episodes: state.episodes,
store: { state, dispatch },
toggleFavAction,
favourites: state.favourites,
};
return (
<section className="episode-layout">
{console.log("props in home return is:", props)}
<EpisodesList {...props} />
</section>
);
};
Console log + what I tried
Maybe the issue is related to this --> the console.log in the return part shows:
props in home return is: {episodes: Array(0), store: {…}}
props in home return is: {episodes: Array(42), store: {…}}
The weird thing is, if I remove the image element from the Episodelist component, it works without errors, all the data is there (I can see it in the console.log, even the image.medium).
Any ideas why I am suddenly getting these errors and how I can reinsert my image element?
All you have to do is use conditional rendering for the image.
<img src={episode.image && episode.image.medium} /> or
<img src={episode.image ? episode.image.medium : "some default image"} />
This happens because on initial render the "image" property of "episode" is null and you are trying to access it like that "episode.null.medium" so you need to add a condition that will try to access the "medium" property only when "episode.image" is not null.

react useEffect hooks with axios cannot read property of undefined

This is based on exercise 2.14 of this course https://fullstackopen.com/en/part2/getting_data_from_server.
The user can select a country, then the weather information for that country's capital will be dislpayed. My code gives me error Cannot read property 'temperature' of undefined
const Weather = ({ city }) => {
const [weatherDetails, setWeatherDetails] = useState([])
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {
params: {
access_key: process.env.REACT_APP_WEATHER_KEY,
query: city
}
}).then(
(response) => {
setWeatherDetails(response.data)
}
)
}, [city])
console.log('weather', weatherDetails);
return (
<div>
<h3>Weather in {city} </h3>
{weatherDetails.current.temperature}
</div>
)}
Basically, the line
{weatherDetails.current.temperature}
makes my code crash. When I do remove that line, I am able to see the response thanks to the console.log, but there are two consecutive logs
weather []
weather {request: {…}, location: {…}, current: {…}}
I figured that my code happens in between these two, and it tries to access the data before it has even arrived, but I don't know what to do to fix this.
Also, I don't know what the argument [city] of useEffect() does, so it'd be great if someone can explain to me what it does.
Edit: Solved!
Set weatherDetail's initial state to null and did some conditional rendering
if (weatherDetails) {
return (
<div>
<h3>Weather in {capital}</h3>
{weatherDetails.current.temperature} Celsius
</div>
)
} else {
return (
<div>
Loading Weather...
</div>
)
}
weatherDetails is an empty array, initially, so there is no current property to read from.
Use some conditional rendering. Use initial null state and then check that it is truthy to access the rest of the object when it is updated.
const Weather = ({ city }) => {
const [weatherDetails, setWeatherDetails] = useState(null) // <-- use null initial state
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {
params: {
access_key: process.env.REACT_APP_WEATHER_KEY,
query: city
}
}).then(
(response) => {
setWeatherDetails(response.data)
}
)
}, [city])
console.log('weather', weatherDetails);
return (
<div>
<h3>Weather in {capital} </h3>
{weatherDetails && weatherDetails.current.temperature} // check that weatherDetails exists before accessing properties.
</div>
)}
What does the argument [city] of useEffect do?
This is the hook's dependency array. Hooks run on each render cycle, and if any values in the dependency array have updated it triggers the hook's callback, in this case, the effect to get weather data when the city prop updates.
useEffect
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.

Resources