I'm making a react-feed app, inspired by Michael's tutorial (there's also a video) and I encountered some trouble trying to pass an array inside an array as props using Lodash's _.map function. This is the information I'm mapping:
const events = [
{
event: 'Gods',
venue: 'Asgard',
venuePicture: 'picture1.jpg',
when: '21:00 27/04/16',
genres: ['rock', 'funk'],
artists: [
{
artist: 'Thor',
artistPicture: 'thor.jpg'
},
{
artist: 'Loki',
artistPicture: 'loki.jpg'
}
]
},
{
event: 'Humans',
venue: 'Midgard',
venuePicture: 'picture2.jpg',
when: '21:00 27/04/16',
genres: ['jazz', 'pop'],
artists: [
{
artist: 'Human1',
artistPicture: 'human1.jpg'
},
{
artist: 'Human2',
artistPicture: 'human2.jpg'
}
]
}
];
I'm passing to the component like this (this works):
renderItems(){
const props = _.omit(this.props, 'events');
return _.map(this.props.events, (event, index) => <EventsFeedItem key={index} {...event} {...props}/>);
}
render() {
return (
<section>
{this.renderItems()}
</section>
);
}
This works perfectly fine, dividing each "event" object
Then I try to destructure and map the "artists:" object of each event like this:
renderArtists() {
const { event, venue, venuePicture, when, genres, artists } = this.props.events;
const props = _.omit(this.props, 'events');
return _.map({artists}, (artist, index) => <ItemArtist key={index} {...artist} {...props}/>);
}
render() {
return (
<ul>
{this.renderArtists()}
</ul>
);
}
This is the result I'm getting, which is close, but not what I need:
I need to separate these further to get:
{artist: "Thor"} {artistPicture: "thor.jpg"}
{artist: "Loki"} {artistPicture: "loki.jpg"}
and so on...
I see there's a pattern here, I just don't know how to implement it further. It breaks when I try to repeat the same destructure then _.map thing. Can anyone please give me a hand with this, sorry for the long post.
return _(this.props.events).flatMap('artists').map((artist, index)=><ItemArtist key={index} {...artist} {...props}/>).value();
Oh, I found the problem thanks to VyvIT's comment (he deleted his comment), it's here:
const { event, venue, venuePicture, when, genres, artists } = this.props.events;
_.map({artists}, (artist, index) => <ItemArtist key={index} {...artist} {...props}/>);
"artists" shouldn't be destructured (curly brackets), should be like this:
_.map(artists, (artist, index) => <ItemArtist key={index} {...artist} {...props}/>);
Thank you so much guys!
Related
I am working on a NextJs project with a Firebase Store.
I access a collection and get an array with objects with a random key and as value the data:
const data = [
{
randomdocid67233: {
name: "ABC",
latinName: "DEF"
}
},
{
randomdocid6d7dddd233: {
name: "GHI",
latinName: "JKI"
}
}
];
Beause I dont know the randomdocid's I cant figure out how to display the name and latinName. I have made a codesandbox with the problem to illustrate it: https://codesandbox.io/s/spring-fast-w0tt4?file=/src/App.js:56-268
Im sure it's actually easy to do but I don't seem to be able to figure it out. Hopefully someone knows!
Thanks,
Santi.
You can use Object.keys() as solution here
{data.map((item, index)=> {
let key=Object.keys(item)[0]
return <div key={key}> // better to use unique key value then index
{item[key].latinName}
</div>
)}
You need to first get the key inside every object and return the value of that key in the map. Update the code based on your need to render the data after you fetch it. You can do it like this
export default function App() {
const data = [
{
randomdocid67233: {
name: "ABC",
latinName: "DEF"
}
},
{
randomdocid67233: {
name: "GHI",
latinName: "JKI"
}
}
];
const newData = data.map(item => {
const key = Object.keys(item)[0];
return item[key]
})
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{newData.map((item, index) => (
<div key={index}>
{item.name} {item.latinName}
</div>
))}
</div>
);
}
I've tried even manually setting the default values like in the documentation but no dice. I'm not sure if it's a styling issue or what. So below I posted what I have along with a screenshot.
<Select
components={animatedComponents}
getOptionLabel={convertToLabel}
getOptionValue={option => option.resource_name}
isMulti
onChange={changeEvent}
options={users}
theme={theme => ({
...theme,
borderRadius: 0
})}
defaultValue={(props.value || []).map(convertToValue)}
value={(props.value || []).map(convertToValue)}
/>
convertToValue function
const convertToValue = props => {
return {
label: `${props.name} ${props.family_name}`,
value: props.resource_name
};
};
convertToLabel function
const convertToLabel = props => {
return `${props.name} ${props.family_name}`;
};
changeEvent function
const changeEvent = (selectedOption, i) => {
let option = {
name: "reviewers",
value: selectedOption
};
update({ target: option });
};
users & props objects
users:
[
{
resource_name: "facebook_user1",
name: "Joe",
family_name: "Dirt"
},
{
resource_name: "facebook_user2",
name: "Trident",
family_name: "White"
}
]
props:
{
field: "placeholder",
fieldType: "placeholderType"
value:[
{
resource_name: "facebook_user1",
name: "Joe",
family_name: "Dirt"
},
{
resource_name: "facebook_user2",
name: "Trident",
family_name: "White"
}
]
}
What I see on my screen.
It is extremely difficult to tell exactly what your issue is, without seeing the actual JSX of your select render. Here are a few issues I see, looking at your question, and some hard guesses at what might be happening.
You should show us the full JSX render of your Select implementation
You never show us what your defaultValue prop looks like, but
remember that value is expected to be equal to one of your
options, not just an option 'value'
Your label and option getter
methods signature should be getOptionLabel = (option) => string and
getOptionValue = (option) => string. You've used props, which
might conflict with parent scope, in your instance.
You probably want
your convertToValue method signature to line up with those as well.
Your onChange event method signature doesn't line up with
React-Select, and may be causing you pain. See my answer to this
recent question for help on this.
I have a json file:
{"places":[{"id":"1","name":"Balzac's Niagara On-The-Lake","logo_url":"http://example.com/store_images/new_82.jpg","website_url":"http://www.example.com","hours":{"monday":"07:00 AM - 07:00 PM","tuesday":"07:00 AM - 07:00 PM"}},{"id":"2","name":"Balzac's Port,"logo_url":"http://example.com/images.jpg","website_url":"http://www.example.com","hours":{"monday":"07:00 AM - 07:00 PM","tuesday":"07:00 AM - 07:00 PM"}}]}
Trying to loop and render the items on browser. Here is my code:
class Dashboard extends Component {
state = {
data: ''
};
componentDidMount() {
axios
.get(url)
.then(resp => resp.data)
.then(data => {
this.setState({ data });
//console.log(data.places[0].id);
console.log(data.places.length);
})
.catch(err => console.log(err));
}
render() {
const { data } = this.state;
let content;
for (let i in data.places) {
console.log(data.places[i].name);
content = <div>{data.places[i].name}</div>;
}
return (
<div className="container">
<h3>{content}</h3>
</div>
);
}
}
I am able to see all the items in the console but not on the UI page.
You have a number of problems here. First, the code is incomplete. So as it this has no hope of working.
Next, you have an array of places not an object, so you cannot use a for-in loop. You'll need to map over those to produce the appropriate markup. See MDN for iterating over various data structures.
Also, do not initialize state.data to an empty string. If you don't strongly type your code, it is good to at least provide an indication of what the type will eventually be. For example here you might supply an empty object ({}) or null. Setting it to an empty string ('') is misleading.
Lastly the JSON you supplied was invalid. Ensure your actual data source is correct. Here that meant correcting a missing double-quote on name property of the second field: "name": "Balzac's Port". There are several online tools for JSON validation.
I've simulated an asynchronous XHR activity here with setTimeout, but the idea is the same:
function simulateXhr() {
return new Promise(resolve => {
setTimeout(() => {
return resolve({
places: [
{
id: "1",
name: "Balzac's Niagara On-The-Lake",
logo_url: "http://example.com/store_images/new_82.jpg",
website_url: "http://www.example.com",
hours: {
monday: "07:00 AM - 07:00 PM",
tuesday: "07:00 AM - 07:00 PM"
}
},
{
id: "2",
name: "Balzac's Port",
logo_url: "http://example.com/images.jpg",
website_url: "http://www.example.com",
hours: {
monday: "07:00 AM - 07:00 PM",
tuesday: "07:00 AM - 07:00 PM"
}
}
]
});
}, 500);
});
}
class Dashboard extends React.Component {
state = {
data: null // or {}
};
componentDidMount() {
simulateXhr().then(data => {
this.setState({ data });
});
}
render() {
return (
<div className="container">
<h3>Places:</h3>
{!this.state.data ? (
"no data yet"
) : (
<ol>
{this.state.data.places.map(place => (
<li>{place.name}</li>
))}
</ol>
)}
</div>
);
}
}
ReactDOM.render(<Dashboard />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
On each iteration of the loop, you're replacing the value of content. That's why on your page it's only displaying the very last item of the array.
In order to display all of the contents of your array, you must iterate over them inside your render().
You can consider using the Array.map method inside of your render method.
i am planning to build a generic filter like Gbif Have.
My question is how to approach this problem.
I like to use ReactJs for this project.
What other technology i need to look into along with React and redux in order to design such a generic filter.
I try to design this filter using React and redux only.
In my approach, i try to maintain the query parameter inside the state variable of the get_data method, in which i am fetching the data from the server. As somebody click on any filter button, then i pass custom event from that filter component along with query parameter and handle this event in get_data method. In get_data method again i am saving this value in get_data state parameter and again getting the new filtered data.
Now the Problem with above approach is that as the number of parameter increases it become very difficult to maintain.
my get_data constructor look like this.
constructor(props){
super(props);
this.state={
params:{
max:10,
offset:0,
taxon:[],
sGroup:[],
classification:undefined,
userGroupList:[],
isFlagged:undefined,
speciesName:undefined,
isMediaFilter:undefined,
sort:"lastRevised",
webaddress:""
},
title:[],
groupName:[],
userGroupName:[],
view:1
}
this.props.fetchObservations(this.state.params)
this.loadMore=this.loadMore.bind(this);
};
The way i am getting data from filter component is something like this.
this is my handleInput method which fire onSelect method from one of the filter.
handleInput(value,groupName){
this.setState({
active:true
})
this.props.ClearObservationPage();
var event = new CustomEvent("sGroup-filter",{ "detail":{
sGroup:value,
groupName:groupName
}
});
document.dispatchEvent(event);
};
the way i am handling this event in my get_data component is look something like this.
sGroupFilterEventListner(e){
const params=this.state.params;
if(!params.sGroup){
params.sGroup=[];
}
console.log("params.sGroup",params.taxon)
params.sGroup.push(e.detail.sGroup)
params.sGroup=_.uniqBy(params.sGroup)
const groupName=this.state.groupName;
var titleobject={};
titleobject.sGroup=e.detail.sGroup;
titleobject.groupName=e.detail.groupName;
groupName.push(titleobject);
let newgroupname=_.uniqBy(groupName,"sGroup")
params.classification=params.classification;
let isFlagged=params.isFlagged;
let speciesName=params.speciesName;
let MediaFilter=params.isMediaFilter;
let taxonparams=params.taxon;
taxonparams= taxonparams.join(",");
let sGroupParams=params.sGroup;
sGroupParams=sGroupParams.join(",");
let userGroupParams=params.userGroupList;
userGroupParams=userGroupParams.join(",");
let newparams={
max:10,
sGroup:sGroupParams,
classification:params.classification,
offset:0,
taxon:taxonparams,
userGroupList:userGroupParams,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
}
this.props.fetchObservations(newparams);
this.setState({
params:{
max:10,
sGroup:params.sGroup,
classification:params.classification,
offset:0,
taxon:params.taxon,
userGroupList:params.userGroupList,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
},
groupName:newgroupname
})
}
I registered and unRegistered the sGroupFilterEventListner in my componentDidMount and componentunmount method.
Presently i am also not considering the case where if somebody type in url bar, the filter panel change automatically.
Please consider all the above scenario and suggest me a generic way to do the same. thanks.
My Current Filter Panle look like this
Here's a quick example (React only, no Redux) I whipped up with a dynamic number of filters (defined in the filters array, but naturally you can acquire that from wherever).
const filters = [
{ id: "name", title: "Name", type: "string" },
{
id: "color",
title: "Color",
type: "choice",
choices: ["blue", "orange"],
},
{
id: "height",
title: "Height",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
{
id: "width",
title: "Width",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
];
const filterComponents = {
string: ({ filter, onChange, value }) => (
<input
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
/>
),
choice: ({ filter, onChange, value }) => (
<select
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
size={1 + filter.choices.length}
>
<option value="">(none)</option>
{filter.choices.map(c => (
<option value={c} key={c}>
{c}
</option>
))}
</select>
),
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { filters: {} };
this.onChangeFilter = this.onChangeFilter.bind(this);
}
onChangeFilter(filterId, value) {
const newFilterState = Object.assign({}, this.state.filters, {
[filterId]: value || undefined,
});
this.setState({ filters: newFilterState });
}
renderFilter(f) {
const Component = filterComponents[f.type];
return (
<div key={f.id}>
<b>{f.title}</b>
<Component
filter={f}
value={this.state.filters[f.id]}
onChange={this.onChangeFilter}
/>
</div>
);
}
render() {
return (
<table>
<tbody>
<tr>
<td>{filters.map(f => this.renderFilter(f))}</td>
<td>Filters: {JSON.stringify(this.state.filters)}</td>
</tr>
</tbody>
</table>
);
}
}
ReactDOM.render(<App />, document.querySelector("main"));
body {
font: 12pt sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
(originally on https://codepen.io/akx/pen/JyemQQ?editors=0010)
Hope this helps you along.
My end result should be this :
const SECTIONS = [
{
title: 'First',
content: <ReactComponent />,
},
{
title: 'Second',
content: <ReactComponent />,
}
];
But I'm not sure if this is terrible. Here's my confused and ignorant attempt at this :
return [
this.props.course.sections
.map( (section, idx) => {title: section.title, content: <ReactComponent /> } );
]
The trouble it seems, is that this errs on the fact that it's not a proper hash so it doesn't recognize the :. Is this even possible in as shorthand as I'm trying to accomplish this?
You need to wrap {} with () to make it object literal not block.
return [
this.props.course.sections
.map( (section, idx) => ({title: section.title, content: <ReactComponent /> }) );
]