Nested array with React and Firestore - How to display? - reactjs

I have a nested array within the individual items in a collection.
{
"id": "RpFRcKLIgELlBLgIOJM4",
"Category": "",
"Method": "",
"User": "rWFZhAKk9eOSIIFoP0DqqvrC6WJ3",
"Foods": [
{
"Weight": 1.065,
"label": "Milk - Long Life (1 Litre) (1.065)",
"value": "Milk-LongLife(1Litre)"
},
{
"label": "Blueberries (0.125)",
"value": "Blueberries",
"Weight": 0.125
}
],
"Name": "456",
"Serves": ""
}
{
"id": "WQ6KBLevFsCdV73j4KU4",
"Category": "",
"Name": "123",
"Foods": [
{
"value": "Wine-White",
"label": "Wine - White"
},
{
"value": "Milk-LongLife(1Litre)",
"label": "Milk - Long Life (1 Litre)"
}
],
"Serves": "",
"User": "rWFZhAKk9eOSIIFoP0DqqvrC6WJ3",
"Method": ""
}
const useItemsMeals = () => {
const user = firebase.auth().currentUser;
const user1 = user.uid;
const [items, setItems] = useState([]); //useState() hook, sets initial state to an empty array
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("meals")
.where("User", "==", user1)
.orderBy("Category", "asc")
.onSnapshot(snapshot => {
const listItemsMeals = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setItems(listItemsMeals);
console.log(listItemsMeals);
});
return () => unsubscribe();
}, []);
return items;
};
I am having a tough time trying to display items from the 'Foods' array, am currently using for my return:
const listItemMeals = useItemsMeals();
{listItemMeals.map(item => (
<TableRow hover key={item.id} id={item.id}>
<TableCell>{item.Category}</TableCell>
<TableCell>{item.Name}</TableCell>
<TableCell>{item.Foods}</TableCell>
When doing this it tells me:
Error: Objects are not valid as a React child (found: object with keys {label, value}). If you meant to render a collection of children, use an array instead.
I think I need to map this nested array again somehow - but for the life of me - cannot figure it out!

You're almost there.
Your useItemsMeals functions loads the data from Firestore, and sets it correctly into the items variable in the state to render it. But then you use const listItemMeals = useItemsMeals() in your rendering code, which messes things up.
You should not try to return any value from useItemsMeals, and instead solely rely on the items variable from the state to pass the information between the database and the rendered.
So:
// return items; 👈 remove this
---
// const listItemMeals = useItemsMeals(); 👈 remove this
---
{items.map(item => ( // 👈 read items from the state instead

You need to loop over the Foods array again. Like this
const listItemMeals = useItemsMeals();
{listItemMeals.map(item => (
<TableRow hover key={item.id} id={item.id}>
<TableCell>{item.Category}</TableCell>
<TableCell>{item.Name}</TableCell>
{
item.Foods.map(food=>(
<div> //this can div or a new row tag
<TableCell>{food.weight}</TableCell>
<TableCell>{food.label}</TableCell>
<TableCell>{food.value}</TableCell>
</div>))
}

Related

I face a problem in the array I get the data from firebase (firestore) and I want to show array of data how can i do?

//data from Firebase
[
{
"Role": "Student",
"Section": "A",
"StudentEmail": "amir#gmail.com",
"StudentFather": "Atif Mian",
"StudentID": "Bs-2000",
"StudentName": "Amir Liaqat",
"Subject": ["Web", "app"],
"uid": "atwYEXNseKYyXfnTpldcTyL2Hj83"
}
]
I want to get only "Subject": ["Web", "app"] in the form of list
I used the code below but I think it was wrong
{subject
? subject.map((a, i) => {
return <Text key={i}>{a}</Text>
})
: <></>
}
I received result "WebApp" but I need in the form of list.
I receive the data from there
const [subject, setSubject] = useState();
firestore()
.collection('users')
.where('uid', '==', StudentUID)
.onSnapshot({
error: e => console.error(e),
next: querySnapshot => {
var data = [];
var sub = [];
querySnapshot.forEach(doc => {
data.push(doc.data());
sub.push(doc.data().Subject);
});
setStudents(data);
setSubject(sub)
console.log(sub)
},
});
Assuming the subject state is the array of Subject properties, also an array, from the data containing objects like the following:
[
{
"Role": "Student",
"Section": "A",
"StudentEmail": "amir#gmail.com",
"StudentFather": "Atif Mian",
"StudentID": "Bs-2000",
"StudentName": "Amir Liaqat",
"Subject": ["Web", "app"],
"uid": "atwYEXNseKYyXfnTpldcTyL2Hj83"
},
]
Then this means the subject state is an array of arrays, i.e. [["Web", "app"], ...]. ["Web", "app"] is valid JSX and will pretty much be rendered directly as is, one string value then the other, with no separator characters, e.g. "Webapp".
The simplest solution would be to join this array and convert it to a string when mapping, specifying the separator character you'd like, i.e. "Web, app".
Example:
const [subjects, setSubjects] = useState([]); // <-- valid initial state
...
firestore()
.collection('users')
.where('uid', '==', StudentUID)
.onSnapshot({
error: e => console.error(e),
next: querySnapshot => {
var data = [];
var sub = [];
querySnapshot.forEach(doc => {
data.push(doc.data());
sub.push(doc.data().Subject);
});
setStudents(data);
setSubjects(sub);
},
});
...
{subjects.map((subject, i) => (
<Text key={i}>{subject.join(", ")}</Text> // `["Web", "app"]` => "Web, app"
)}
There is no need really to duplicate this subject state, you can easily map it from the students state.
{students.map(({ StudentID, Subject }) => (
<Text key={StudentID}>{Subject.join(", ")}</Text> // "Web, app"
)}

When I try to change one state of an object the other also changes (React)

I'm using an object in two different states, in on of the states I just set the object in the state, and the other state I change one of the values in the object before setting state, but when I make the change both of the states change.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = [];
return [...prev, i];
});
});
};
both states return an empty favmovies array
[
{
"id": "1",
"name": "development",
"image": "img.png",
"favmovies": []
},
{
"id": "2",
"name": "socialmedia",
"image": "img2.png",
"favmovies": []
},
{
"id": "3",
"name": "writing",
"image": "img2.png",
"favmovies": []
}
]
Issue
You are still mutating an object reference:
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = []; // <-- mutation is here
return [...prev, i];
});
});
};
i["favmovies"] = []; mutates the i object that is still a reference in the categoryList1.movies array.
Solution
From what I can tell, you want to store categoryList1.movies array into the allCategory state, and then a copy of categoryList1.movies array with the favmovies array emptied/reset to an empty array.
Instead of for-each iterating over the categoryList1.movies array and enqueueing multiple state updates, just map categoryList1.movies array to a new array reference for the movies state.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
setMovies(movies => movies.concat( // <-- add to existing state
categoryList1.movies.map(movie => ({ // <-- map to new array
...movie, // <-- shallow copy movie object
favmovies: [], // <-- update property
}))
));
};

How can I acces a nested object from my API

I made an API with spring-boot that is able to create Cook objects with Recipes objects in it. The idea is that one cook has a relationship with (multiple) recipe(s). When I perform a get request to my server, I am able to show the entire (Cook) object with nested (Recipe) object(s). I am also able to render Cook object properties to the browser, e.g name, age etc. However, I am not able to render Recipe properties to the browser from within the Cook object. It always gives me an undefined.
My code for the API request that shows the name from a cook correctly :
const [posts, setPosts] = useState([]);
useEffect(()=> {
axios.get(" http://localhost:9090/api/cooks")
.then(response => {
console.log(response.data) //response.data.recipe.x doesn't seem to work.
setPosts(response.data);
})
.catch(error => {
console.log(error)
})
},[])
return (
<div>
{posts.map(post => <li key={post.id}>{post.name}</li>)}; //post.recipe.x also doesn't work
</div>
)
}
const Index = () => {
return (
<div>
<Alt />
</div>
)
}
The raw JSON data from the server looks like this:
{
"id": 1,
"name": "bart",
"surName": "Janssen",
"gender": "male",
"age": 3,
"recipes": [
{
"id": 1,
"recipeName": "Hamburger",
"portion": 1,
"meat": "Ground meat",
"vegetable": "Tomato, Pickles, other greens",
"other": "salt, pepper, mustard, ketchup",
"instructions": "bla bla"
},
{
"id": 2,
"recipeName": "tomato soup",
"portion": 2,
"meat": null,
"vegetable": "tomato",
"other": "salt, stock",
"instructions": "bla bla"
}
]
}
]
However, I really need to access the Recipe properties too.
I tried accessing recipes through response.data[0], response.data[0][0], response.data[0][1], response.data[[0][0]] However, they all seem to give me an undefined back or an error.
I also tried to use JSON.Stringify(response.data), but this didn't give me any successes too.
I really could use some help. Thanks in advance :)
try to use useEffect with async/await
useEffect(async () => {
const result = await axios(
'http://localhost:9090/api/cooks',
);
setData(result.data.recipes);
});
and another moment, you initialize
const [posts, setPosts] = useState([]);
as an array - useState([ ]), but trying to write a data as object -
{
"id": 1,
"name": "bart",
"surName": "Janssen",
....
}
that's why you need to straight extract recipes
setData(result.data.recipes);

how do I Mock API and following the same approach as my static array?

I have a React hooks component, which uses an HTML div-alike table to render data, and the data is being fetched from the server. I need to test the component to see if the table has data by mocking the API call. Below is the current code. I want to remove the use of arr completely and make avoid getting {users} rendered as text/plain as you can you in the below image
const arr = [
{
"demo": [
{
"_id": "T0810",
"title": "Historian",
"tags": [
"demo"
],
"queries": [],
},
{
"_id": "T0817",
"title": "book",
"tags": [
"demo"
],
"queries": [],
},
],
"demo_2": [
{
"_id": "T0875",
"title": "Program",
"tags": [
"demo_2",
"Control"
],
"queries": [],
},
{
"_id": "T0807",
"title": "Interface",
"tags": [
"demo_2"
],
"queries": [],
}
]
}];
const keys = Object.keys(arr[0]);
export default function Demo () {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const handleOnClick = async () => {
try {
const { data } = await axios.get('https://run.mocky.io/v3/0d7aa6e3-fc01-4a47-893d-7e1cc3013d4e');
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
return (
<div className="container">
<>
{keys.map((key) => (
<div className="col" key={key}>
<div className="row">{key}</div>
{arr[0][key].map((item) => (
<div className="row" key={item.technique_id} onClick={() => handleOnClick(item)}>{item.technique}</div>
))}
</div>
))}
</>
{isModalOpen && <Modal onRequestClose={() => setModalIsOpen(false)} data={users}/>}
</div>
);
}
Second attempt...Assuming I understand the ask then a package like Nock can do the trick (https://github.com/nock/nock).
The approach is to allow Nock to mock the API and return the result locally without calling the server. In your case it will be something like:
const nock = require('nock');
const arr = [...]; // your original definition of arr
const scope = nock('https://run.mocky.io')
.get('/v3/0d7aa6e3-fc01-4a47-893d-7e1cc3013d4e')
.reply(200, arr);
// The rest of your code "as is"
...
This setup of Nock will intercept every HTTP call to https://run.mocky.io and will specifically return the content of arr in response to a GET /v3/0d7aa6e3-fc01-4a47-893d-7e1cc3013d4e.
Typically, I would not put this code in the main code.
Instead you can use it as part of a testing script that runs without dependency on an active server. E.g. testing during build.

How to access data returned from props

I new to nextjs and react. I am using the nextjs to fetch data from api and display it in view. this is my code.
const Index = props => (
<Layout>
<h1>Events</h1>
<ul>
{props.eventList.map(({ venue }) => (
<li key={venue.name}>
<a>{venue.name}</a>
</li>
))}
</ul>
</Layout>
);
Index.getInitialProps = async function() {
const res = await fetch("api link here");
let data = await res.json();
console.log(`Show data fetched. Count: ${data.events.length}`);
return {
eventList: data.events
};
};
Now my api return data like this
[
{
"id": 4303920,
"name": "Test Event",
"venue": {
"id": 1610,
"name": "Eiffel Tower Viewing Deck at Paris Las Vegas",
}
},
{
"id": 4323,
"name": "Test Event 2",
"venue": {
"id": 1610,
"name": "Eiffel Tower Viewing Deck at Paris Las Vegas",
}
}
]
I am able to access the venue details by props.eventList.map function but i am not able to access the id and name.
In your map instead of ({venue}) => just do (item) => your issue is you are getting each element and only extracting venue from it instead of the entire element. Once you have item you can say item.id, item.venue etc.
You should not put {venue} inside your map function but pass the event like this (event) then you will have even.name and event.is and you will have event.venue.id and event.venue.name

Resources