React variable evaluation when 'push' JSX elements in an array - javascript

I'm pushing JSX elements in an array
for (category of this.state.categories) {
categories.push(
<li>
<label
className="tree-toggler nav-header list-group-item"
onClick={() => this.showHideConnectorNames(category)}
>
{category}
</label>
<ul
className="tree ul-no-style"
id={category+'Connectors'}
>
</ul>
</li>
);
}
Problem is that category variable in this.showHideConnectorNames(category) method call evaluates to last category in this.state.categories array (so 'cat2' for all elements). It is evaluated correctly at all the other places, such as inside the <label>.
So I have to do this:
for (category of this.state.categories) {
if (category === 'cat1')
categories.push(
<li>
<label
className="tree-toggler nav-header list-group-item"
onClick={() => this.showHideConnectorNames('cat1')}
>
{category}
</label>
<ul
className="tree ul-no-style"
id={category + 'Connectors'}
>
</ul>
</li>
);
else if (category === 'cat2')
categories.push(
<li>
<label
className="tree-toggler nav-header list-group-item"
onClick={() => this.showHideConnectorNames('cat2')}
>
{category}
</label>
<ul
className="tree ul-no-style"
id={category + 'Connectors'}
>
</ul>
</li>
);
else
categories.push(
<li>
<label
className="tree-toggler nav-header list-group-item"
onClick={() => this.showHideConnectorNames(category)}
>
{category}
</label>
<ul
className="tree ul-no-style"
id={category + 'Connectors'}
>
</ul>
</li>
);
}
Is this a React issue or am I doing something wrong?

Not sure what you want to do with the array after it's built, but I think using a map call would be quite a bit more efficient. Just place this inside your return statement wherever you want to render your list items:
<ul>
{this.state.categories.map(cat => (
// your li has a missing key property
<li>
<label
className="tree-toggler nav-header list-group-item"
onClick={() => this.showHideConnectorNames(cat)}
>
{cat}
</label>
<ul
className="tree ul-no-style"
id={cat + 'Connectors'}
></ul>
</li>
))}
</ul>

You are using for of incorrectly to prevent using it incorrectly I like to use map instead:
const categories = this.state.categories.map(category => (
// not sure what to use for key here but it's missing
<li key={category.id}>
<label
className="tree-toggler nav-header list-group-item"
onClick={() => this.showHideConnectorNames(category)}
>
{category}
</label>
<ul
className="tree ul-no-style"
id={category + 'Connectors'}
></ul>
</li>
));
This is because category is set in every loop iteration so after the loop is finished category is the last one. That is why by the time you click on your component the category is always the last one from this.state categories.
Here is a demo showing the bahavior:
let category,categories=[],cats=[1,2,3];
//you re assing category every time
for (category of cats) {
categories.push(
()=>console.log('category is now:',category)
);
}
//now category is the last one
console.log('category after loop:',category);
//now I execute the functions category is the last one
categories.forEach(cat=>cat())

just add let, then category will be local inside for scope
for (let category of this.state.categories)

Related

Conditional classname not changing even after value changes

I'm building a page with two tabs. When one tab is clicked, the other tab should have the className="nav-link active" and the other tab should switch to className="nav-link" and vice-versa.
Here's my code:
import react from 'react'
const account = () => {
const [activeTab, tabActive] = react.useState("true")
return (
<>
<div className='container my-5'>
<ul className="nav nav-tabs">
<li className="nav-item">
<button type="button" className={"nav-link " + (activeTab?'active':'')} onClick={() => tabActive("true")}>Profile details</button>
</li>
<li className="nav-item">
<button type="button" className={"nav-link " + (activeTab?'':'active')} onClick={() => tabActive("false")}>Select Colleges</button>
</li>
</ul>
</div>
<div className='container my-5'>
{
activeTab === "true" && <p>Value is true</p>
}
{
activeTab === "false" && <p>Value is false</p>
}
</div>
</>
)
}
export default account
The aforementioned code should be able to do what I expect, however, I can't get the active class removed and added to the buttons as expected. It's either being applied to both buttons at the same time or two none at all.
Non-empty strings are always truthy. Use booleans instead.
const account = () => {
const [activeTab, tabActive] = react.useState(true);
return (
<>
<div className='container my-5'>
<ul className="nav nav-tabs">
<li className="nav-item">
<button
type="button"
className={"nav-link " + (activeTab ? 'active' : '')}
onClick={() => tabActive(true)}
>
Profile details
</button>
</li>
<li className="nav-item">
<button
type="button"
className={"nav-link " + (activeTab ? '' : 'active')}
onClick={() => tabActive(false)}
>
Select Colleges
</button>
</li>
</ul>
</div>
<div className='container my-5'>
{activeTab ? <p>Value is true</p> : <p>Value is false</p>}
</div>
</>
)
};
Update
For more than 2 tabs use an index or GUID, anything that uniquely identifies a tab, to store in state for the active tab, and check this value for matching when rendering.
Example:
const account = () => {
const [activeTab, tabActive] = react.useState(0);
return (
<>
<div className='container my-5'>
<ul className="nav nav-tabs">
<li className="nav-item">
<button
type="button"
className={"nav-link " + (activeTab === 0 && 'active')}
onClick={() => tabActive(0)}
>
Profile details
</button>
</li>
<li className="nav-item">
<button
type="button"
className={"nav-link " + (activeTab === 1 && 'active')}
onClick={() => tabActive(1)}
>
Select Colleges
</button>
</li>
<li className="nav-item">
<button
type="button"
className={"nav-link " + (activeTab === 2 && 'active')}
onClick={() => tabActive(2)}
>
Select Something Else
</button>
</li>
</ul>
</div>
<div className='container my-5'>
{activeTab === 0 && <p>Active tab is 0</p>}
{activeTab === 1 && <p>Active tab is 1</p>}
{activeTab === 2 && <p>Active tab is 2</p>}
</div>
</>
)
};

Vue how to add element to v-for loop

In my vue-app I want to add an element inside a v-for-loop. So I tried to do this:
<li v-for="(slide, index) in slides" :key="index"
:class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
</li>
<li class="cursor-pointer item" v-if="slides.length === 5">foobar</li>
but this doesn't keep the "new" item inside the index e.g. I want the element to be inside the loop.
How can I solve that?
Do you mean that if slides.length === 5, then add your second <li> tag after each <li> tag?
If yes, please take a look at the following code
<template v-for="(slide, index) in slides">
<li
:key="slide.nodeName"
:class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
</li>
<li
:key="`${slide.nodeName}_${index}`"
class="cursor-pointer item"
v-if="slides.length === 5"
>
foobar
</li>
</template>
Please pay attention to the key value binding of the <li> tag at the same level
<el-row v-for="item in queryFolderList" :key="item.id">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-button-group>
<el-button type="primary" size="mini" #click="goToDetil(item.id)"
>{{ item.id }}</el-button
>
<el-button type="warning" size="mini" #click="submit(item.id)"
>{{ item.name}}</el-button
>
</el-button-group>
</el-col>
To include elements to the loop of other elements in vue.js, you need to include them into their tag. In your case you need to be aware of, that nesting <li> into <li> can cause problems. Try this:
<li v-for="(slide, index) in slides" :key="index"
:class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
<ul v-if="slides.length === 5">
<li class="cursor-pointer item">foobar</li>
</ul>
</li>
As the loop is on your first <li> element, everything between <li> and </li> of that element will be included into the loop. I added an <ul> around your second <li> for the nested list.
EDIT: Render second <li> if 5 slides in slides
So now that I think to know what you trying to achieve, let´s try to solve it. The second <li> has to be rendered after 5 loops done/if there are 5 slides in slides.
First you need to note, that v-if="slides.length === 5" only matches, if there are exactly 5 slides. So if you now provide something with a slides.length bigger than 5 or smaller, your <li> with foobar don´t show. Change it to v-if="slides.length > 5" instead. If <li> with foobar only should be shown if the length IS 5, then your code is working as it should.
But if you try to add <li> with foobar AFTER 5 items or even every 5 items, you could use this little trick:
<ul>
<span v-for="(slide, index) in slides" :key="index">
<li :class="slideIndex === index ? 'active' : ''"
#click="slideIndex = index"
>
{{ slide.nodeName }}
</li>
<li class="cursor-pointer item" v-if="(index + 1)%5 === 0">foobar</li>
</span>
</ul>
You shouldn´t include <span> in <ul>, but it should work. Now the loop runs for both <li> elements, but <li> with foobar only renders after each 5th element, due to v-if="(index + 1)%5 === 0".
Hope this helped you.

ReactJS problem when calling function inside .map at jsx block code

I am trying to call to an arrow function inside jsx statement while using .map() js function:
<ul className="list-group col-10 ms-5">
{datoPaises.map((item) => (
<li className="list-group-item" key={item.id}>
{this.parseRegions(item)}
<span> Muertes nuevas: {item.today_new_deaths} </span>
<span>Muertes totales: {item.today_deaths}</span>
</li>
))}
</ul>
this.parseRegions(item) is returning TypeError: Cannot read property 'parseRegions' of undefined
which is not happening when just writing the item inside the jsx block
<ul className="list-group col-10 ms-5">
{datoPaises.map((item) => (
<li className="list-group-item" key={item.id}>
{item.id}
<span> Muertes nuevas: {item.today_new_deaths} </span>
<span>Muertes totales: {item.today_deaths}</span>
</li>
))}
</ul>
this one display all data correctly, the problem appears when trying to use the function.

Mapping array inside array with React

I have a database in MongoDB and one of the props in the document contains an array. I'm trying to map the collection in the client-side using React using this code:
<div>
<h2>Recipes</h2>
<ul>
{this.state.recipes.map(recipe =>
<li key={recipe._id}>
{recipe.title}<br/>{recipe.category}<br/>
{recipe.ingredients}<br/>{recipe.preparation}
</li>
)}
</ul>
</div>
ingredients prop is an array and I want to map it too. How can I do it?
Any help would be great, thanks!
Just map it the same way you do with recipes
<div>
<h2>Recipes</h2>
<ul>
{this.state.recipes.map(recipe =>
<li key={recipe._id}>
{recipe.title}<br/>
{recipe.category}<br/>
{recipe.ingredients.map(ingredient => (
<span>{ingredient}</span>
))}
<br/>{recipe.preparation}
</li>
)}
</ul>
</div>
just map it again
<div>
<h2>Recipes</h2>
<ul>
{this.state.recipes.map(recipe =>
<li key={recipe._id}>
{recipe.title}<br/>
{recipe.category}<br/>
{recipe.ingredients.map(k =>(
<span key={k._id}>{k}<span>
)<br/>
{recipe.preparation}
</li>
)}
</ul>
</div>
<div>
<h2>Recipes</h2>
<ul>
{this.state.recipes.map(recipe =>
<li key={recipe._id}>
{recipe.title}<br/>{recipe.category}<br/>
{ recipe.ingredients.map(ingredient =>
<span>{ingredient}</span>
)}
<br/>{recipe.preparation}
</li>
)}
</ul>
</div>
<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>
You can also use .map() there by calling recipe.ingredients.map() as the following:
{this.state.recipes.map(recipe =>
<li key={recipe._id}>
{recipe.title}<br/>{recipe.category}<br/>
{
recipe.ingredients.map((elem, index) =>
<div key={index}>
{elem /* or maybe elem['your-property-name'] if it's not a string */}
</div>
)
}<br/>{recipe.preparation}
</li>
)}
Also don't forget to add key={index} there to avoid the warning based on Lists and Keys.

When selecting item from an array renders all items react js

I have the following problem, when selecting an item through a checkbox and pressing a button so that that selected item is added to my new array, all the items are added.
I use the function map and return the component with their respective key and item:
renderFiltersInlcuye() {
if(this.props.incluye){
return this.props.incluye.map((item, index) =>{
return <BusquedaPaquetesFiltrosAplicadosItems
key = {item.value}
item= {item}
onClickClose={this.removeItemIncluye.bind(this,index)}
/>
})
}
}
And in my other component I render the following:
render () {
return(
<ul>
<li>
<span className="badge badge-info " >
<button onClick={this.props.onClickClose} type="button" className="close" ><i className="fas fa-times-circle"></i> </button>
{this.props.item.texto}
</span>
</li>
</ul>
);
}

Categories

Resources