I know how to run loops inside react but how do I do it inside an object which is already inside an array being looped?
I am trying to display each ingredient item as an <li>, so far I have got it working with recipe but I am lost with ingredient. If anyone could chime in, I'd appreciate it.
var Recipes = React.createClass({
// hook up data model
getInitialState: function() {
return {
recipeList: [
{recipe: 'Cookies', ingredients: ['Flour ', 'Chocolate']},
{recipe: 'Cake', ingredients: ['Flour ', 'Sprinkles']},
{recipe: 'Onion Pie', ingredients: ['Onion ', 'Pie-Crust']}
]
}
},
loop: function() {
{this.state.recipeList.flatMap('ingredients').map(item, index) => (
<li key={index} className="list-group-item">{ingredient.ingredients}</li>
)}
},
render: function() {
return (
<div>
{this.state.recipeList.map((item, index) => (
<div className="panel panel-default">
<div className="panel-heading"><h3 className="panel-title">{item.recipe}</h3></div>
<div className="panel-body">
<ul className="list-group">
{this.loop}
</ul>
</div>
</div>
)
)}
</div>
);
}
});
How about this way :
loop: function(ingredients) {
return ingredients.map((ingredient, index) => {
return (<li key={index} className="list-group-item">{ingredient}</li>)
})
},
render(){
...
{this.loop(item.ingredients)}
...
}
One more thing, you shouldn't use index of array as key because it will be difficult to manage when editting the array later. It will be better if you assign key with something very unique like id or index + Date.now()
You seem to be missing a return statement in the loop method.
You can cascade rendering as deep as you'd wish, only remember that you need to call the method instead of just placing it in the component structure (see this.loop without call parentheses in your sample):
var myComponent = React.createClass({
renderListElements: function (parent) {
return this.state.listElements[parent].map((element, index) => (
<li
className="my-component__sub-component__list-element"
key={`${parent.uid}_${element.uid}`}
>
{element.name}
</li>
));
},
render: function () {
var parentsId = [ 0, 1, 2, 3 ];
return (
<div className="my-component">
{parentsId.map((item, index) => (
<div
className="my-component__sub-component"
key={item.uid}
>
{this.renderListElements(item)}
</div>
)}
<div/>
);
}
});
Related
I am trying to iterate an array inside another loop in my react app. I have a json file that contains data that looks like this:
[
{
"id":"0001",
"photos":[
"IMG_9239.JPG",
"2019-01-07.jpg",
"IMG_9261.JPG"
]
},
{
"id":"0002",
"photos":[
"IMG_9239.JPG",
"2019-01-07.jpg",
"IMG_9261.JPG"
]
},
{
"id":"0003",
"photos":[
"IMG_9239.JPG",
"2019-01-07.jpg",
"IMG_9261.JPG"
]
}
]
And this is my react component:
const ListItems = ({data}) => {
return (
<div id="items-container">
{data.map( item => (
<p>{item.id}</p>
//iterate the [photos] array in an img tag
//<img src ="photo" />
))}
</div>
)
}
I am trying {item.photos.map....} but it seems like it's not a valid syntax. Can you please help?
Multiple JSX elements require a parent, or at least a psuedo-parent. Here, you can use a fragment <> as the parent of what gets returned from the .map callback, enclosing both the <p> and the <img>s:
const ListItems = ({data}) => {
return (
<div id="items-container">
{data.map( item => (
<>
<p>{item.id}</p>
{item.photos.map(({ src }) => <img src={src}></img>}
</>
))}
</div>
)
}
So I am learning React, and I've tried searching for solutions to my problem both on stackoverflow and on React's own documentation, but I am still stumped.
Essentially, I have a list of 10 subreddits that is being mapped to list items in the form of the subredditsArray variable.
I render the results, and try to pass the selected item when I click that list item to my getSubredditInfo function. However, this doesn't work - event.target.key is undefined. (To clarify, I am looking to grab the key of the single list element that I have clicked).
When I try to just get event.target, I get the actual htmlElement (ex: <li>Dota2</li>), where as I want to get the key, or at least this value into a string somehow without the tags. I also tried putting my onClick method in the list tag of the map function, but that did not work.
Here is the relevant code:
//this is where I get my data
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(event){
//console.log(event.target.key); <-- DOESNT WORK
}
render() {
var subredditsArray = this.state.subreddits.map(function(subreddit){
return (<li key={subreddit.toString()}>{subreddit}</li>);
});
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul onClick={this.getSubredditInfo}>{subredditsArray}</ul>
</div>
);
}
My questions essentially boil down to:
How do I grab the key value from my list object?
Additionally, is there a better way to generate the list than I currently am?
Thank you in advance.
EDIT: Added my componentDidMount function in hopes it clarifies things a bit more.
try the following code:
class App extends React.Component {
constructor(props){
super(props);
this.state = {subreddits:[]};
}
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(subreddit){
console.log(subreddit);
}
render() {
return <div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>
{
this.state.subreddits.map((subreddit)=>{
return (<li key={subreddit.toString()} onClick={()=>this.getSubredditInfo(subreddit)}>{subreddit}</li>);
})
}
</ul>
</div>;
}
}
ReactDOM.render(
<App/>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
please check the onClick event handler now. its an arrow function and its calling the getSubredditInfo function with your subreddit now. so you will get it there.
so its basically different way of calling the handler to pass data to the handler.
it works as you expect it to.
You can use lamda function or make component for item list which have own value for getSubredditInfo function
getSubredditInfo(value) {}
render() {
var subredditsArray = this.state
.subreddits.map((subreddit, i) =>
(<li key={i}
onClick={() => this.getSubredditInfo(subreddit)}>{subreddit}</li>));
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
1) Key should be grabbed either by the id in your object in array. Or you can combine the 2 properties to create a unique key for react to handle re-renders in a better way.
If you have a string array, you may use a combination of string value + index to create a unique value, although using index is not encouraged.
Given a quick example for both below.
2) A better way could be to move your map function into another function and call that function in render function, which will return the required JSX. It will clean your render function.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
subredditsObjArray: [
{ id: 1, value: 'A'},
{ id: 2, value: 'B'},
{ id: 3, value: 'C'},
{ id: 4, value: 'D'}
],
subredditsArray: ['A', 'B', 'C', 'D'],
selectedValue: ''
};
}
getSubredditInfo = (subreddit) => {
console.log(subreddit)
this.setState({
selectedValue: ((subreddit && subreddit.id) ? subreddit.value : subreddit),
});
}
render() {
return (
<div className="redditResults">
<p>Selected Value: {this.state.selectedValue}</p>
<h1>Top {this.state.subredditsArray.length || '0'} subreddits for that topic</h1>
<p>With Objects Array</p>
<ul>
{
this.state.subredditsObjArray
&& this.state.subredditsObjArray.map(redditObj => {
return (<li key={redditObj.id}><button onClick={() => this.getSubredditInfo(redditObj)}>{redditObj.value || 'Not Found'}</button></li>);
})
}
</ul>
<br />
<p>With Strings Array</p>
<ul>
{
this.state.subredditsArray
&& this.state.subredditsArray.map((reddit, index) => {
return (<li key={reddit + '-' + index}><button onClick={() => this.getSubredditInfo(reddit)}>{reddit || 'Not Found'}</button></li>);
})
}
</ul>
</div>
);
}
}
ReactDOM.render(
<App etext="Edit" stext="Save" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Are you trying to do this? I'm not sure what you want to do.
getSubredditInfo(e, subreddit) {
console.log(subreddit)
}
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
this.getSubredditInfo(e, subreddit)
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
The key purpose is to pass your subreddit to the onClick function so you will receive the value while you click the item.
If you still get error try this and tell me what's happened.
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
console.log(subreddit.toString())
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
In a React JS component I am rendering a list of items (Recipes), using JS map function from an array, passed in from a App parent component. Each item has a sub list (Ingredients), again rendered using map function.
What I want is to show/hide the sub list of Ingredients when you click on the Recipe title. I use a onClick function on the title that sets the CSS to display none or block, but I get the following error:
Uncaught TypeError: Cannot read property 'openRecipe' of undefined
Here is my code:
var App = React.createClass({
getInitialState(){
return{
showModal:false,
recipeKeys: [ ],
recipes: [ ]
}
},
addRecipeKey: function(recipe){
var allKeys = this.state.recipeKeys.slice();
var allRecipes = this.state.recipes.slice();
allKeys.push(recipe.name);
allRecipes.push(recipe);
localStorage.setObj("recipeKeys", allKeys);
this.setState({recipeKeys: allKeys, recipes: allRecipes}, function(){
console.log(this.state);
});
},
componentDidMount: function(){
var dummyRecipes = [
{
"name": "Pizza",
"ingredients": ["Dough", "Tomato", "Cheese"]
},
{
"name": "Sushi",
"ingredients": ["Rice", "Seaweed", "Tuna"]
}
]
if(localStorage.getItem("recipeKeys") === null){
localStorage.setObj("recipeKeys", ["Pizza", "Sushi"]);
dummyRecipes.forEach(function(item){
localStorage.setObj(item.name, item);
});
this.setState({recipeKeys: ["Pizza", "Sushi"], recipes: dummyRecipes}, function(){
console.log(this.state);
});
} else {
var recipeKeys = localStorage.getObj("recipeKeys");
var recipes = [];
recipeKeys.forEach(function(item){
var recipeObject = localStorage.getObj(item);
recipes.push(recipeObject);
});
this.setState({recipeKeys: recipeKeys, recipes: recipes}, function(){
console.log(this.state);
});
}
},
open: function(){
this.setState({showModal:true});
},
close: function(){
this.setState({showModal:false});
},
render: function(){
return(
<div className="container">
<h1>Recipe Box</h1>
<RecipeList recipes = {this.state.recipes} />
<AddRecipeButton openModal = {this.open}/>
<AddRecipe closeModal = {this.close} showModal={this.state.showModal} addRecipeKey = {this.addRecipeKey}/>
</div>
)
}
});
var RecipeList = React.createClass({
openRecipe: function(item){
var listItem = document.getElementById(item);
if(listItem.style.display == "none"){
listItem.style.display = "block";
} else {
listItem.style.display = "none";
}
},
render: function(){
return (
<ul className="list-group">
{this.props.recipes.map(
function(item,index){
return (
<li className="list-group-item" onClick={this.openRecipe(item)}>
<h4>{item.name}</h4>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" id={index} >
{item.ingredients.map(function(item){
return (
<li className="list-group-item">
<p>{item}</p>
</li>
)
})}
</ul>
</li>
)
}
)
}
</ul>
)
}
});
ReactDOM.render(<App />, document.getElementById('app'));
Also, I am trying to use a CSS method here, but maybe there is a better way to do it with React?
Can anyone help me? Thanks!
your issue is you are losing your this context in your map... you need to add .bind(this) to the end of your map function
{this.props.recipes.map(function(item,index){...}.bind(this))};
I answered another question very similar to this here. If you can use arrow functions it auto binds for you which is best. If you can't do that then either use a bind or make a shadow variable of your this context that you use inside the map function.
Now for the cleanup part. You need to clean up your code a bit.
var RecipeList = React.createClass({
getInitialState: function() {
return {display: []};
},
toggleRecipie: function(index){
var inArray = this.state.display.indexOf(index) !== -1;
var newState = [];
if (inArray) { // hiding an item
newState = this.state.display.filter(function(item){return item !== index});
} else { // displaying an item
newState = newState.concat(this.state.display, [index]);
}
this.setState({display: newState});
},
render: function(){
return (
<ul className="list-group">
{this.props.recipes.map(function(item,index){
var inArray = this.state.display.indexOf(index) !== -1;
return (
<li className="list-group-item" onClick={this.toggleRecipie.bind(this, index)}>
<h4>{item.name}</h4>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" id={index} style={{display: inArray ? 'block' : 'none'}} >
{item.ingredients.map(function(item){
return (
<li className="list-group-item">
<p>{item}</p>
</li>
)
}.bind(this))}
</ul>
</li>
)
}.bind(this))
}
</ul>
)
}
});
This may be a little complicated and you may not want to manage a list of indicies to toggle a view of ingredients. I'd recommend you make components for your code, this way its more react centric and it makes toggling a view much easier.
I'm going to write this in ES6 syntax also as you should be using ES6.
const RecipieList = (props) => {
return (
<ul className="list-group">
{props.recipes.map( (item,index) => <RecipieItem recipie={item} /> )
</ul>
);
};
class RecipieItem extends React.Component {
constructor(){
super();
this.state = {displayIngredients: false}
}
toggleRecipie = () => {
this.setState({displayIngredients: !this.state.displayIngredients});
}
render() {
return (
<li className="list-group-item" onClick={this.toggleRecipie}>
<h4>{item.name}</h4>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" style={{display: this.state.displayIngredients ? 'block' : 'none'}} >
{this.props.recipie.ingredients.map( (item) => <IngredientItem ingredient={item} /> )}
</ul>
</li>
);
}
}
const IngredientItem = (props) => {
return (
<li className="list-group-item">
<p>{props.ingredient}</p>
</li>
);
};
You also can use something like this:
render: function(){
var self = this;
return (
<ul className="list-group">
{this.props.recipes.map(
function(item,index){
return (
<li className="list-group-item" onClick={self.openRecipe(item)}>.....
Here, i have included a my example code. If it is one dimensional array means, i can easily insert json data's into my code. How to achieve this one with multidimensional json data with react js?
var Category = React.createClass({
render: function() {
return (
<div>
{this.props.data.map(function(el,i) {
return <div key={i}>
<div>
{el.product}
</div>
<div>
{el.quantity}
</div>
</div>;
})}
</div>
);
}
});
var data = [
{
product:"a",
quantity:28,
sub:[
{
subItem:'a'
},
{
subItem:'b'
}
]
},
{
product:"b",
quantity:20,
sub:[
{
subItem:'a'
},
{
subItem:'b'
}
]
}
];
React.render(<Category data={data}/>, document.body);
You can create component for sub categories like this,
var SubCategory = React.createClass({
render: function () {
var list = this.props.data.map(function(el, i) {
return <li key={i}>{ el.subItem }</li>;
});
return <ul>{ list }</ul>;
}
});
and use it in Category component
{this.props.data.map(function(el,i) {
return <div key={i}>
<div>{el.product}</div>
<div>{el.quantity}</div>
<SubCategory data={ el.sub } />
</div>;
})}
Example
I am facing a very similar problem to this question, but I am fetching data using a Promise and want to render it into the DOM when it comes through. The console.log() displays all the items correctly. I think my problem is that the lodash.map returns an array of <li> elements, and so I am trying to call this.renderItems() in order to render (but renderItems() doesn't seem to exist). Am I doing something unconventional, is there an easier way, is there an equivalent function to replace my renderItems()?
renderArticleHeadline: function(article) {
console.log('renderArticleHeadline', article.headline);
return (
<li>
{article.headline}
</li>
)
},
render: function() {
return (
<div>
<ul>
{
this.renderItems(
this.fetchFrontPageArticles().then(data => {
lodash.map(data, this.renderArticleHeadline)
})
)
}
</ul>
</div>
);
}
It should be something like this
getInitialState: function() {
return {
items: []
};
},
renderArticleHeadline: function(article) {
return (
<li>
{article.headline}
</li>
);
},
componentDidMount: function() {
this.fetchFrontPageArticles().then(data => {
this.setState({
items: data
});
});
},
render: function() {
var items = lodash.map(this.state.items, this.renderArticleHeadline);
return (
<div>
<ul>
{items}
</ul>
</div>
);
}
P.S. read thinking in react