I am mapping over some data and I am trying to use some of the data as the options for the semantic-ui-react Dropdown component, but the data wont load in even if I add .toArray() to the end of it, however if I change the data structure to not use Immutable, it works fine. I mainly wanted to understand why so I could get a better understanding of things.
Here is the mapping of the data with immutable:
{
labelingData.map((item, i) => {
return (
<span key={i}>
{
item.get('categories').map((category, c) => {
return (
<span key={c}>
{
category.get('subcategories').map((subcategory, sc) => {
return (
<span key={sc}>
{
subcategory.get('name') === 'Labels'
? <Dropdown
defaultValue={'Bananas'}
options={subcategory.get('childItems').toArray()}
/>
: null
}
</span>
);
})
}
</span>
);
})
}
</span>
);
})
}
I do get an error in the console about unknown props but I am not adding these props in my code:
Unknown props `_root`, `__ownerID`, `__hash`, `__altered` on <div> tag. Remove these props from the element
The api of semantic-ui-react Dropdown options is to pass in an array of objects that look like:
{ text: '', value: '' }
However, when you call .toArray() on a Immutable.List containing some Immutable.Map it will not .toJS() the Maps.
eg.
var list = Immutable.fromJS({ value: 1 }, 2]);
console.log(list.toArray()); // prints [Immutable.Map, 2]
So you are actually giving Dropdown an array of Maps and the Dropdown doesn't use .get to get the values from the object.
Another thing is that React prefers to receive arrays rather than immutable.Lists.
So you could use a reduce function rather than a map function to create a normal array of react components:
item.get('categories').reduce((result, category, c) => {
result.push(<Category key={c} category={category} />);
return result;
}, []);
Related
I have a diary object with 2 meals
function Magicdiary() {
const [diary, setDiary] = React.useState<MagicDiaryDay[]>([
{ mealName: "Breakfast", ingredient: null },
{ mealName: "Lunch", ingredient: null },
]);
return (
<div>
<p>meal 1: {diary[0].ingredient?.productName}</p>
<Button onClick={() => console.log(diary[0].ingredient?.productName)}>
log diary
</Button>
{diary.map((meal, index) => {
return (
<MealComponentForMagicDiary
diary={diary}
setDiary={setDiary}
index={index}
/>
);
})}
</div>
);
}
I have selection of ingredients that I call from my backend, and everytime I select current ingredient, I set it to the diary:
// MealComponentForMagicDiary
useEffect(() => {
if (hit) {
const diaryCopy = diary;
diaryCopy[index].ingredient = {
productName: hit.productName,
nutrients: {
"energy-kcal_serving": hit.calories,
protein_serving: hit.protein,
carbohydrates_serving: hit.carbs,
fat_serving: hit.fats,
},
};
setDiary(diaryCopy);
}
}, [hit, selectedHit]);
As you can see meal 1 is empty, but when I log it on the console I can see the correct productName what is the cause of this bug?
You are updating the state in the wrong way, you are mutating the original array that is overwriting the exiting array, Instead, you need to do it in an immutable way that is providing a new Instance of diary whenever you want to update it, you can do in the following way
useEffect(() => {
if (hit) {
const diaryCopy = diary.map((d, ind) => {
if (ind === index) {
// The Diary ingredient you want to update
d.ingredient = {
productName: hit.productName,
nutrients: {
"energy-kcal_serving": hit.calories,
protein_serving: hit.protein,
carbohydrates_serving: hit.carbs,
fat_serving: hit.fats,
}
};
}
return d;
} );
setDiary(diaryCopy);
}
}, [hit, selectedHit]);
From my limited react experience, when funny things like this happen I have a few go-to methods to try. One of them is to call an empty function when passing a function down as a prop. I.e. instead of:
<MealComponentForMagicDiary
diary={diary}
setDiary={setDiary}
index={index}
/>
try:
<MealComponentForMagicDiary
diary={diary}
setDiary={() => setDiary}
index={index}
/>
I'd love to know why this works sometimes, if anybody (does anybody?) understands react properly.
My fingers are crossed that it works for you!
It's hard to say exactly with this given information but I'm inclined to say this:
You're not getting the information you want because when the component renders, it's not there. Take for example this:
<p>meal 1: {diary[0].ingredient?.productName}</p>
You check if ingredient exists but are you sure diary[0] exists? Since you're setting this data elsewhere in a useEffect, I suspect that it's not available at render - even though you can console it.
I suggest using the React Developer Tools to look at your component tree and see what that state looks like when it's rendered.
That's all I can guess without more code.
I have an array of objects that comes from an API, but the content of this array is dynamic.
I want to map this data to an HTML table in React, using the array.map function, but I don't know how I could access the variable.property.
For example:
{
array.map(data=> {
<td>{data.DynamicProperty}</td>
})
}
What should I put in DynamicProperty ?
Could anybody show to me what could be the better approach for this case?
You can simply get all values within object using Object.values and then map over them to return table columns in case you have more than one keys in each object. Or you could simply return <td>{Object.values(data)[0]}</td> if you have only one key in each object
{
array.map(data=> {
return (
<>
{Object.values(data).map(val => <td>{data}</td>)}
</>
)
})
}
Since the data is dynamic, what I had to do was take the property names and storing in the component hook's state:
const [mappableFields, setMappableFields] = useState([]);
useEffect(() => {
if (array && array.length > 0) {
setMappableFields(Object.getOwnPropertyNames(array[0]));
}
}, [array]);
Then I mapped the array and the mappableFields, creating the <td> by reading each data by it's key, like this:
{ array.map((data, index) => (
<tr key={index}>
{
mappableFields.map((header, idx) => (
<td key={idx}>{data[header]}</td>
))
}
</tr>
))
}
I fetch the data from this link.
I want to show the data based on a recent date. I used the map method to render the data then try to use sort them based on date. But I got an error. I used the moment package to making the date organized. Sorry for my English, I don't know if express my feeling right.
This is my React's component setup
import React, { useEffect, useState } from "react";
import axios from "axios";
import moment from "moment";
const Events = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
fetchingData();
}, []);
const fetchingData = () => {
axios
.get("/events")
.then(response => {
console.log("response", response);
setEvents(response.data.data);
})
.catch(err => console.log(err));
};
//do console.log
console.log("events", events);
return (
<div>
{events.map(list => {
return (
<div>
<div className="card">
<div className="carddd">
<div className="card_image">
<ul>
<li>
{moment(list.event_dates.starting_day.toString())
.calendar()
.sort((a, b) => { // I try sort date in here.
return a - b;
})}
</li>
</ul>
<div>
{list.description.images.map(img => {
return (
<img
src={img.url}
alt="jj"
style={{ width: "250px", height: "250px" }}
/>
);
})}
</div>
</div>
</div>
</div>
</div>
);
})}
</div>
);
};
export default Events;
Screenshot below
Going by Moment's documentation on .calendar() here it returns a string:
https://momentjs.com/docs/#/displaying/calendar-time/
Javascript strings do not have a .sort() function, so you're trying to call a function that doesn't exist which is causing your error.
You will want to sort the dates before converting them to strings, so you'll want to run sort on your events array before you do the map.
Solution edit:
So your response comes back and you have the events in state under events.
Before you render, you could reduce your events data down to just being dates, keyed using the ID of each event.
const eventDates = events.reduce((acc, event) => {
acc[event.id] = {
startDate: moment(event.event_dates.starting_day),
endDate: moment(event.event_dates.ending_day),
// You could also add more info in here, like the name or location.
}
return acc;
}, {});
Then use Object.keys() to get an array (which can be sorted, objects can't.) of the keys. Then sort that array by accessing the values in eventDates, using the keys in the array, like so.
let sortedByStartDate = Object.keys(eventDates);
sortedByStartDate.sort(
(a, b) => eventDates[a].startDate - eventDates[b].startDate
);
The keys will now be sorted by the start dates. (By the way, to sort moment objects, you can just use the - operator on them).
Now when you are rendering you want to map through the sortedByStartDate array, using the keys to access values in eventDates (you may also want to add additional information in here in the reduce to get access to, instead of just dates).
sortedByStartDate.map(
eventKey => <li>{eventDates[eventKey].startDate.calendar()}</li>
);
I'm using react-select to create a Select option in my create-react-app and am trying to map over an array of objects to generate the options. My app loads fine but when I click on the Select I get this error: Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
I'm passing the data to the component via props which is working fine, and the data is structured like this:
const guests = [
{
name: 'Kait',
plus: true,
plusName: 'Kitty'
},
{
name: 'Séanin',
plus: true,
plusName: 'Guest'
}
]
And here's the Select component:
<Select
value={selectedOption}
onChange={this.handleChange}
options={
this.props.guests.map((guest, index) => {
return {
label: guest,
value: guest,
key: index
}
})
}
/>
Any ideas on how I can fix this?
Sung M. Kim‘s answer is correct, but there is an easier way to use your attributes as label and value without remapping your options array.
Using the props getOptionLabel and getOptionValue you can keep your object mappings. Both accept a function that gets a single option as an argument and returns the value or label from the appropriate object property as string.
<Select
options={this.props.guests}
getOptionLabel={(option) => option.name}
{ /* Couldn't find a value in your structure, so I used name again */ }
getOptionValue=((option) => option.name}
{ ... }
/>
See documentation for more.
You probably will have to generate the array before rendering the component
const options = this.props.guests.map((guest, index) => {
return {
label: guest.name,
value: guest,
key: index
}
})
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
Edit:
is because you are passing an object in the label field. You should pass a String instead
The error occurs because the label is set as guest (an object) not as guest.name (a string).
Making following change will work.
<Select
value={selectedOption}
onChange={this.handleChange}
options={
this.props.guests.map((guest, index) => {
return {
- label: guest,
+ label: guest.name
value: guest,
key: index
}
})
}
/>
You can try it out in the sandbox link below.
i have an issue trying to create a "typehead" funcitonality in my app, i have an "input" that listen to onChange, and that onChange is calling to a Redux reducer that search for a tag in hole store, i need to retrive all matches with my search, over here everything is ok, but when i delete my search, my hole store is equals to my filtered results, and i want that when my search is empty it returns hole my store. (gif and code)
case 'SEARCH_BY_TAG':
let tag = action.tag
let filtered = state.slice(0)
if(tag != ""){
const copied = state.filter(item => {
return item.tags.find(obj => {
if(obj.name.indexOf(tag) > -1){
return true
}
})
})
return filtered = copied.filter(Boolean)
}else{
return filtered
}
break;
Instead of filtering things out inside your reducer, do it directly on render(), there is nothing wrong with that.
You can still use the SEARCH_BY_TAG action to keep track of the search keyword and use it to apply the filter when rendering your list.
I think you should refactor your state to change it from this:
[ item1, item2, item3, ... ]
to this:
{
query: '',
options: [ item1, item2, item3, ... ]
}
This way you can do what #Raspo said in his answer - do the filtering of the options in your render function.
Currently when you change the text in the search field, you are dispatching this action:
{
type: 'SEARCH_BY_TAG',
tag: 'new query'
}
I think you should change the action name, and the reducer code, to look more like this:
// note this is almost exactly the same as the old action
{
type: 'CHANGE_AUTOCOMPLETE_QUERY',
query: 'new query'
}
and the reducer could then change to this:
case CHANGE_AUTOCOMPLETE_QUERY:
return Object.assign({}, state, {
query: action.query
})
Note that in the reducer case I just wrote, the options part of the state isn't changed at all. It remains constant.
Now let's assume your current render function looks something like this:
const options = reduxState // not sure exactly how you get the state but whatever
return (
<div>
{options.map(option => {
<Option option={option} />
})}
</div>
)
This code relies on getting the state as an array. You could change it in the new setup to do this:
const query = reduxState.query
const options = reduxState.options.filter(item => {
return item.tags.find(obj => {
if(obj.name.indexOf(query) > -1){
return true
}
})
})
return (
<div>
{options.map(option => {
<Option option={option} />
})}
</div>
)