React render array returned from map - javascript

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

Related

Show/Hide (sub) list items with React JS

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)}>.....

"Cannot read property 'props' of undefined" React Issue

I'm building an app in React based on this tutorial.
Instead of using the updated es2016, I'm using an older way, so I'm having some trouble with the challenges that come. I got this error in the browser: "TypeError: Cannot read property 'props' of undefined". I assume it's pointing to the {this.props.onDelete} part. Here's a snippet of my code for the Notes.jsx component:
var Notes = React.createClass({
render: function () {
return (
<ul>
{this.props.notes.map(
function(note) {
return (
<li key={note.id}>
<Note
onTheDelete={this.props.onDelete}
task={note.task} />
</li>
);
}
)}
</ul>
);
}
});
And here's a snippet from App.jsx, it's parent:
var App = React.createClass({
getInitialState: function () {
return {
notes: [
{
id: uuid.v4(),
task: 'Learn React'
},
{
id: uuid.v4(),
task: 'Do laundry'
}
]
}
},
newNote: function () {
this.setState({
notes: this.state.notes.concat([{
id: uuid.v4(),
task: 'New task'
}])
});
},
deleteNote: function() {
return 'hi';
},
render: function () {
var {notes} = this.state;
return (
<div>
<button onClick={this.newNote}>+</button>
<Notes notes={notes} onDelete={this.deleteNote}/>
</div>
);
}
});
I deleted the actually useful parts from deleteNote to make sure no issues were there. I'm having a difficult time wrapping my head around using "this" and what the binding is doing in the tutorial I mentioned.
this inside the map function isn't the same as this outside of it because of how JS works.
You can save off this.props.onDelete and use it w/o the props reference:
render: function () {
var onDelete = this.props.onDelete;
return (
<ul>
{this.props.notes.map(
function(note) {
return (
<li key={note.id}>
<Note
onTheDelete={onDelete}
task={note.task}
/>
</li>
);
}
)}
</ul>
);
}
Unrelated, but I'd move that map function into its own function and avoid the deep nesting.
Dave Newton's answer is entirely correct, but I just wanted to add that if you use ES6 arrow functions then you can avoid having to keep an additional reference to this, as well as removing the return statement and taking advantage of the implicit return syntax.
var Notes = React.createClass({
render: function () {
return (
<ul>
{this.props.notes.map(
note => {(
<li key={note.id}>
<Note
onTheDelete={this.props.onDelete}
task={note.task} />
</li>
)}
)}
</ul>
);
}
});

How do I loop through an array in an array of objects

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/>
);
}
});

How to insert multidimensional array data in react js?

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

Search in Backbone Collections, Updating UI with React

I'm spending time on something probably simple:
I'd like to implement a search bar, ideally updating the list of item as-you-type. My small app uses React and Backbone (for models and collections).
Displaying the list isn't too hard, it all works perfectly doing this (the mixin i'm using basically allows easy collections retrieval):
var List = React.createClass ({
mixins: [Backbone.React.Component.mixin],
searchFilter: function () {
//some filtering code here, not sure how (filter method is only for arrays...)
}
}
getInitialState: function () {
initialState = this.getCollection().map(function(model) {
return {
id: model.cid,
name: model.get('name'),
description: model.get('description')
}
});
return {
init: initialState,
items : []
}
},
componentWillMount: function () {
this.setState({items: this.state.init})
},
render: function(){
var list = this.state.items.map(function(obj){
return (
<div key={obj.id}>
<h2>{obj.name}</h2>
<p>{obj.description}</p>
</div>
)
});
return (
<div className='list'>
{list}
</div>
)
}
});
Now i've tried with no success to first translate the backbone collection into "state" with the getInitialState method, my idea was to proxy through a copy of the collection, which then could hold the search results. I'm not showing here my attemps for the sake of clarity(edit: yes i am), could someone guide me to the right approach? Thanks in advance.
There are many ways to accomplish this, but the simplest (in my opinion) is to store your search criteria in the List component's state and use it to filter which items from your collection get displayed. You can use a Backbone collection's built in filter method to do this.
var List = React.createClass ({
mixins: [Backbone.React.Component.mixin],
getInitialState: function () {
return {
nameFilter: ''
};
},
updateSearch: function (event) {
this.setState({
nameFilter: event.target.value
});
},
filterItems: function (item) {
// if we have no filter, pass through
if (!this.state.nameFilter) return true;
return item.name.toLowerCase().indexOf(this.state.nameFilter) > -1;
},
render: function(){
var list = this.props.collection
.filter(this.filterItems.bind(this))
.map(function(obj){
return (
<div key={obj.id}>
<h2>{obj.name}</h2>
</div>
)
});
return (
<div className='list'>
{list}
<input onChange={this.updateSearch} type="text" value={this.state.nameFilter}/>
</div>
)
}
});
var collection = new Backbone.Collection([
{
name: 'Bob'
},
{
name: 'Bill'
},
{
name: 'James'
}
]);
React.render(<List collection={collection}/>, document.body);
jsbin
The search criteria could easily be passed down from a parent component as a prop, so the search input does not have to live inside your List component.
Eventually I also found a different solution (below), but it involves copying the entire collection into state, which is probably not such a good idea...
var List = React.createClass ({
mixins: [Backbone.React.Component.mixin],
searchFilter: function () {
var updatedlist = this.state.init;
var searchText = this.refs.searchbar.getDOMNode().value
updatedlist = updatedlist.filter(function (item) {
return item.name.toLowerCase().search(
searchText.toLowerCase()) !== -1
});
this.setState({items: updatedlist})
}
},
getInitialState: function () {
initialState = this.getCollection().map(function(model) {
return {
id: model.cid,
name: model.get('name'),
description: model.get('description')
}
});
return {
init: initialState,
items : []
}
},
componentWillMount: function () {
this.setState({items: this.state.init})
},
render: function(){
var list = this.state.items.map(function(obj){
return (
<div key={obj.id}>
<h2>{obj.name}</h2>
<p>{obj.description}</p>
</div>
)
});
return (
<div className='list'>
<input ref='searchbar' type="text" placeholder="Search" onChange={this.searchFilter}/>
{list}
</div>
)
}
});

Categories

Resources