"this" is undefined inside map function Reactjs - javascript

I'm working with Reactjs, writing a menu component.
"use strict";
var React = require("react");
var Menus = React.createClass({
item_url: function (item,categories,articles) {
console.log('afdasfasfasdfasdf');
var url='XXX';
if (item.type == 1) {
url = item.categoryId == null ? 'javascript:void(0)' : path('buex_portal_browse_category', {slug: categories[item.categoryId].slug});
} else if (item.type == 2) {
url = item.articleId == null ? 'javascript:void(0)' : path('buex_portal_view_article', {slug: articles[item.articleId].slug, id: item.articleId});
} else {
url = item.url;
}
return url;
},
render: function () {
// console.log(this.props.menus); // return correctly
var menuElements = this.props.menus.map(function (item1) { // return fault : 'cannot read property 'props' of undefined '
return (
<div>
<li>
<a href={this.item_url(item1, this.props.categories, this.props.articles )}>{item1.name} // the same fault above
<i class="glyphicon glyphicon-chevron-right pull-right"></i>
</a>
<div class="sub-menu">
<div>
{item1._children.map(function (item2) {
return (
<div>
<h4>
<a href={this.item_url(item2, this.props.categories, this.props.articles)}>{ item2.name }</a>
</h4>
<ul>
{item2._children.map(function (item3) {
return (
<div><li><a href={this.item_url(item3, this.props.categories, this.props.articles) }>{ item3.name }</a></li></div>
);
})}
</ul>
</div>
);
})}
</div>
</div>
</li>
</div>
);
});
return (
<div class="menu">
<ul class="nav nav-tabs nav-stacked">
{menuElements}
</ul>
</div>
);
}
});
Whenever I use 'this' inside map function it is undefined, but outside it is no problem.
The error:
"Cannot read property 'props' of undefined"
Anybody help me ! :((

Array.prototype.map() takes a second argument to set what this refers to in the mapping function, so pass this as the second argument to preserve the current context:
someList.map(function(item) {
...
}, this)
Alternatively, you can use an ES6 arrow function to automatically preserve the current this context:
someList.map((item) => {
...
})

Related

Prevent Mouseenter bubbling in an unorderest list of links in Vue 3

I have tried a lot of different things trying to stop event bubbling on the mousenter event but still have the problem, when I hover on a link the event triggers for all the links, like all of them were hovering at the same time. Is an unordered list of links and I'm using a computed value to dynamically change the inline styles when I hover the links:
My component is something like this:
<section class="categoryList">
<ul>
<li v-for="category in categories" :key="category.id">
<a
#mouseenter.stop="mouseOver()"
#mouseleave.stop="mouseOver()"
class="category"
:style="getStyle"
href="#"
#click.prevent="getCategory(category.name)"
>{{ category.name }}</a
>
</li>
</ul>
</section>
And this is the computed value:
const getStyle = computed(() => {
if (props.primaryColor != undefined && props.primaryColor != "") {
if (!hover.value) {
return `background-color:${props.primaryColor};color:${props.secondaryColor}`;
} else {
return `background-color:${props.secondaryColor};color:${props.primaryColor}`;
}
} else {
if (!hover.value) {
return `background-color:#614EFB;color:white`;
} else {
return `background-color:#523dfa;color:white`;
}
}
});
And then a standard function to control the hover state dynamically:
function mouseOver() {
hover.value = !hover.value;
}
If I understood you correctly try to set hover as id instead of boolean:
const { ref, computed } = Vue
const app = Vue.createApp({
setup() {
const props = ref({primaryColor: null, secondaryColor: null})
const categories = ref([{id: 1, name: 'aaa'}, {id: 2, name: 'bbb'}, {id: 3, name: 'ccc'}])
const hover = ref(null)
function mouseOver(id) { hover.value = id; }
function mouseExit() { hover.value = null }
getCategory = () => {}
const getStyle = (id) => {
if (props.primaryColor != undefined && props.primaryColor != "") {
if (id !== hover.value) {
return `background-color:${props.primaryColor};color:${props.secondaryColor}`;
} else {
return id === hover.value && `background-color:${props.secondaryColor};color:${props.primaryColor}`;
}
} else {
if (id !== hover.value) {
return `background-color:#614EFB;color:white`;
} else {
return `background-color:red;color:white`;
}
}
};
return { props, categories, mouseOver, getStyle, getCategory, hover, mouseExit }
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<section class="categoryList">
<ul>
<li v-for="category in categories" :key="category.id">
<a
#mouseenter="mouseOver(category.id)"
#mouseleave="mouseExit()"
class="category"
:style="getStyle(category.id)"
href="#"
#click.prevent="getCategory(category.name)"
>{{ category.name }}</a
>
</li>
</ul>
{{hover}}
</section>
</div>

React: Rendering a method defined inside arrow function?

Hello friends! I hope you are well.
I've got an arrow function called WorldInfo and its parent component is passing down an object in props that for the sake of this example, I'm just calling object. Now In WorldInfo I also want to parse and list the items in object, so I've created the method serverInfoTabList to take object and shove it through .map. My problem is when compiled, my browser does not recognize serverInfoTabList either when it's defined nor called in WorldInfo's own return function.
Here is the error and the code itself.
Line 7:5: 'serverInfoTabList' is not defined no-undef
Line 34:22: 'serverInfoTabList' is not defined no-undef
const WorldInfo = (props) => {
serverInfoTabList = (object) => {
if (object != undefined){
return object.item.map((item) => {
const time = Math.trunc(item.time/60)
return (
<li key={item._id}>{item.name}
<br/>
Minutes Online: {time}
</li>
);
});
}
}
return (
props.object!= undefined ?
<div className={props.className}>
<h1>{props.world.map}</h1>
{/* <img src={props.object.image}/> */}
<div>
<ul>
{serverInfoTabList(props.object)}
</ul>
</div>
</div>
:
null
);
}
Thanks for your time friendos!
You forgot the const declaration
const serverInfoTabList = (object) => {
/* ... */
}
The other problem is that you're accessing properties which doesn't exist props.world for instance. Also you're mapping through an undefined property props.object.item. I've corrected your sandbox
const WorldInfo = props => {
const serverInfoTabList = object => {
return Object.keys(object).map(key => {
const item = object[key];
const time = Math.trunc(item.time / 60);
return (
<li key={item._id}>
{item.name}
<br />
Minutes Online: {time}
</li>
);
});
};
return props.object ? (
<div className={props.className}>
<h1>{props.world.map}</h1>
{/* <img src={props.object.image}/> */}
<div>
<ul>{serverInfoTabList(props.object)}</ul>
</div>
</div>
) : null;
};
class Todo extends Component {
render() {
const object = { item1: { _id: 1, time: 1 }, Item2: { _id: 2, time: 2 } };
return (
<div>
<WorldInfo object={object} world={{ map: "foo" }} />
</div>
);
}
}

Enzyme `.find(selector)` does not seem to find a selector

element.find('span.active-nav-option') returns nothing whilst element.find('.active-nav-option') returns the span. The point of the test is to find out if a span was rendered instead of a Link.
Component is as follows:
const PageNav = ({
router,
slides,
}) => (
<nav className="PageNav">
<span className="chevron">
<MoreVerticalIcon
strokeWidth="0.5px"
size="3em"
/>
</span>
<ul className="nav-links">
{mapSlides(slides, router)}
</ul>
<style jsx>{styles}</style>
</nav>
)
function mapSlides(slides, router) {
return Object.entries(slides)
.sort((
[, { order: a }],
[, { order: b }],
) => a - b)
.map(([slidename, { altText, order }]) => {
const isActiveLink = router.query.slidename === slidename
const navItemClassnames = [
'nav-item',
isActiveLink && 'active',
]
.filter(Boolean)
.join(' ')
const Element = isActiveLink
? props => <span {...props} />
: Link
const liInternalElementProps = {
...(isActiveLink && { className: 'active-nav-option' }),
...(!isActiveLink && {
href: `/CVSlide?slidename=${slidename}`,
as: `/cv/${slidename}`,
}),
}
return (
<li
className={navItemClassnames}
key={order}
>
<Element {...liInternalElementProps}>
<a title={altText}>
<img
src={`/static/img/nav-icons/${slidename}.svg`}
alt={`An icon for the ${slidename} page, ${altText}`}
/>
</a>
</Element>
<style jsx>{styles}</style>
</li>
)
})
}
To Reproduce
run this line as a test:
const wrapperOne = shallow(
<PageNav
slides={mockSlides}
router={{
query: {
slidename: 'hmmm',
},
}}
/>
)
const spanExists = wrapperOne
.find('.active-nav-option')
.html() // outputs <span class="active-nav-option">...</span>
// so one would expect span.active-nav-option to work?
const spanDoesNotExist = wrapperOne
.find('span.active-nav-option')
.html() // throws an error `Method “html” is only meant to be run on a single node. 0 found instead.`
// subsequently if I use `.exists()` to test if the element exists, it returns nothing.
Expected behavior
element.find('span.active-nav-option') should return the span. I think? I initially thought this was to do with shallow vs mount but the same happens with mount. Am I being an idiot here? Is this something to do with the map function in the component?
OS: OSX
Jest 23.5
enzyme 3.5.0
Looks like that's because you don't use <span/> directly in JSX returned from render method but assign it to a variable and then add that variable to JSX.
If you execute console.log(wrapperOne.debug()) you will see the following result (I've removed styles and components that you didn't provided):
<nav className="PageNav">
<span className="chevron" />
<ul className="nav-links">
<li className="nav-item active">
<Component className="active-nav-option">
<a title={[undefined]}>
<img src="/static/img/nav-icons/0.svg" alt="An icon for the 0 page, undefined" />
</a>
</Component>
</li>
</ul>
</nav>
As you can see, you have <Component className="active-nav-option"> instead of <span className="active-nav-option"> thus span.active-nav-option can't find anything.

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

Categories

Resources