Inserting an element after every 'X' React components - javascript

I have a React component that renders a list of items into three columns using Bootstrap's col col-md-4 styles. However, I need to insert a clearfix div after every third element to ensure that the next 'row' of elements displays in the correct place.
My current render code looks like this:
render() {
var resultsRender = $.map(this.state.searchResults, function (item) {
return <Item Name={ item.Name } Attributes={ item.Attributes } />;
}
return (
<div>{ resultsRender }</div>
);
}
Item simply renders a div with the col classes, containing the passed-in content:
render() {
return(
<div className='col col-md-4'>
...content here...
</div>
);
}
My current workaround is to pass the index of the Item in as a prop, and then apply the clearfix class to the Item if the index is a multiple of 3, but this feels a bit hackish to me and I would prefer a separate div to allow me to only show the clearfix on the required viewport size (using Bootstrap's visible-* classes).
I'm sure there must be a more elegant way to solve this problem than the one I've come up with. Any suggestions are appreciated.

You could iterate your array and add a <div/> every 3 items:
render() {
var items = $.map(this.state.searchResults, function (item) {
return <Item Name={ item.Name } Attributes={ item.Attributes } />;
}
var resultsRender = [];
for (var i = 0; i < items.length; i++) {
resultsRender.push(items[i]);
if (i % 3 === 2) {
resultsRender.push(<div className="clearfix" />);
}
}
return (
<div>{ resultsRender }</div>
);
}

Related

Div onClick function doesn't work when clicking in React app

I have a function which creates 2 divs when changing the number of items correspondingly (say if we choose 5 TVs we will get 5 divs for choosing options). They serve to make a choice - only one of two possible options should be chosen for every TV, so when we click on one of them, it should change its border and background color.
Now I want to create a dynamic stylization for these divs: when we click on them, they should get a new class (tv-option-active) to change their styles.
For that purposes I used 2 arrays (classesLess and classesOver), and every time we click on one of divs we should remove a class if it's already applied to the opposite option and push the class to the target element - thus only one of options will have tv-option-active class.
But when I click on a div I do not get anything - when I open the document in the browser and inspect the elements, the elements do not even receive new class on click - however, when I console log the classes variable that should apply to an element, it is the way it should be - "less tv-option-active" or "over tv-option-active". I applied join method to remove the comma.
I checked the name of imported css file and it is ok so the problem is not there, also I applied some rules just to make sure the problem is not there and it worked when it's not dynamic (I mean no clicks are needed).
So my list of reasons causing that trouble seems to be not working.
I also tried to reorganize the code in order to not call a function in render return - putting mapping directly to render return, but this also didn't work.
Can anyone give me a hint why it is that?
Here is my code.
import React from 'react'
import { NavLink, withRouter } from 'react-router-dom'
import './TVMonting.css'
import PageTitle from '../../PageTitle/PageTitle'
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
},
}
render() {
let classesLess = ['less']
let classesOver = ['over']
const tvHandlers = {
tvs: {
decrease: () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
},
increase: () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
},
},
}
const createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
return divsArray.map((i) => {
return (
<React.Fragment key={i}>
<div
className={classesLess.join(
' '
)}
onClick={() => {
const idx = classesOver.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesLess.splice(
idx,
1
)
}
classesLess.push(
'tv-option-active'
)
}}
>
Under 65
</div>
<div
className={classesOver.join(
' '
)}
onClick={() => {
const idx = classesLess.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesOver.splice(
idx,
1
)
}
classesOver.push(
'tv-option-active'
)
// classesOver.join(' ')
}}
>
Over 65
</div>
</React.Fragment>
)
})
}
return (
<div>
<button onClick={tvHandlers.tvs.decrease}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={tvHandlers.tvs.increase}>
+
</button>
{createDivs()}
</div>
)
}
}
export default withRouter(TVMontingRender)
CSS file is very simple - it just adds a border.
P.S. I know that with this architecture when I click on one of the divs all the divs will get tv-option-active class, but for now I just want to make sure that this architecture works - since I'm relatively new in React 🙂Thanks in advance!
Components won't have their lifecycle triggered if you are mutating a variable. You need a state for that purpose, which stores the handled data.
In your case you need some state to say which div has the active class, under or over. You can also abstract each rendered Tv to another Class component. This way you achieve independent elements that control their own class, rather than changing all others.
For that I created a Tv class, where I simplified some of the logic:
class Tv extends React.Component {
state = {
activeGroup: null
}
// this will update which group is active
changeActiveGroup = (activeGroup) => this.setState({activeGroup})
// activeClass will return 'tv-option-active' if that group is active
activeClass = (group) => (group === this.state.activeGroup ? 'tv-option-active' : '')
render () {
return (
<React.Fragment>
<div
className={`less ${ activeClass('under') }`}
onClick={() => changeActiveGroup('under')}
>
Under 65
</div>
<div
className={`over ${ activeClass('over') }`}
onClick={() => changeActiveGroup('over')}
>
Over 65
</div>
</React.Fragment>
)
}
}
Your TvMontingRender will be cleaner, also it's better to declare your methods at your class body rather than inside of render function:
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
}
}
decreaseTvs = () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
}
increaseTvs = () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
}
createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
// it would be better that key would have an unique generated id (you could use uuid lib for that)
return divsArray.map((i) => <Tv key={i} />)
}
render() {
return (
<div>
<button onClick={this.decreaseTvs}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={this.increaseTvs}>
+
</button>
{this.createDivs()}
</div>
)
}
}
Note: I didn't change the key you are passing to Tv, but when handling an array that you manipulate somehow it's often better to pass an unique id identifier. There are some libs for that like uuid, nanoID.
When handling complex class logic, you may consider using libs like classnames, that would make your life easier.

How to make bootstrap rows while going through a list in react and making elements

This is how I am currently doing it - it is very uneloquent, and it doesn't work. I am getting
TypeError: Cannot read property '0' of undefined
at grid.push(<Card key={index+x} name={list[index+x][0]} img={list[index+x][1]}/>);
this is my code
const list = // my list of data
var grid = [];
list.map((element, index) => {
if (index%4 == 0) {
if (index<list.length-2) {
const el = (<div className="row">
//card being a component I built
<Card key={index} name={element[0]} img={element[1]}/>
<Card key={index+1} name={list[index+1][0]} img={list[index+1][1]}/>
<Card key={index+2} name={list[index+2][0]} img={list[index+2][1]}/>
<Card key={index+3} name={list[index+3][0]} img={list[index+3][1]}/>
</div>)
grid.push(el);
} else {
let remainder = (list.length-index);
for (var x=0;x+=1;x<remainder) {
grid.push(<Card key={index+x} name={list[index+x][0]} img={list[index+x][1]}/>);
}
}
}
})\
return (
<div>
{grid}
</div>
)
The current way I am doing it is making grid then iterating through the list and adding jsx to it. The problem is that I need to make a row for bootstrap every 4 columns to make the grid, any better ideas, solutions greatly appreciated!
You are using index+1, index+2, index+3 which will never exist in the array, when you reach last element.
Instead calculate it in some other variable to find elements which exist.

scrollIntoView using React refs

We are trying to scroll to a specific component when the user closes another component.
Our example is very similar to that down below, taken from https://reactjs.org/docs/refs-and-the-dom.html#exposing-dom-refs-to-parent-components
function CustomComponents(props) {
const items = [1,2,3,4].map((x, i) => return (
<div ref={props.inputRef} key={i}>
x + hello
</div>
)
return (
<div>
{ items }
</div>
);
}
function Parent(props) {
return (
<div>
<CustomComponents inputRef={props.inputRef} />
</div>
);
}
class Grandparent extends React.Component {
render() {
return (
<Parent
inputRef={el => this.inputElement = el}
/>
);
}
}
We are rendering a big list of cards and want to be able to scroll to a specific card by calling this.refs[elem].scrollIntoView()
But our problem is that calling this.refs returns an empty object at all levels, and so we are unable to attach to a specific element and then fire it when the user arrives back to view this component.
Would like some help on how one would go about solving this issue.
Where are you trying to call this.refs[elem].scrollIntoView()? You should be working with refs inside componentDidMount or componentDidUpdate, not inside the render method.
I've solved my specific issue by providing an if statement written below in my Grandparent component.
inputRef={el => {
if (el && el.id == this.state.selectedBuildingRef) {
this.myelement.scrollIntoView();
window.scrollBy(0, -65);
}
}}

Styling divs created from array onClick in React

So i have an array like this const divs = ["Text 1","Text 2","Text 3"].
I create divs (a small menu) from this array in my render function
var createThreeDivs = divs.map((category) => {
return <div key={category} onClick={this.handleClick} className="myDivClass">{category}</div>
});
I want to style one of these divs when i click on them, and the remove the styling on the rest of them. So when i select one of the divs it gets a color and removes the color on the rest of them
In normal javascript with no virtual DOM i could do like this:
handleClick(e) {
//remove styling from others
var allDivs = document.getElementsByClassName("myDivClass");
for(var i = 0; i < allDivslength; i++) {
allDivs[i].classList.remove("myDivClass-styled");
}
//Add styling class to selected,
e.target.classList.add("myDivClass-styled");
}
But this manipulate the DOM directly. How do i do something like this in React?
I have seen examples of how this can be done using state with only one element and by not having an array creating the divs. But i can't come up with a good solution for this scenario. Any suggestions?
Using the component's state you can update the color based on the active div. Update the index of the active div when the user clicks, and when the index equals the div that was clicked on update the color of that div.
See example below.
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
active: 0
};
}
render() {
const divs = ["Text 1", "Text 2", "Text 3"];
const updateActiveDiv = (value) => {
this.setState(() => {
//this line will reset the value to
//null if same element is clicked twice
if(value === this.state.active) {
value = null;
};
return {
active: value
}
});
};
let divText = divs.map((div, i) => {
let color = this.state.active === i ? 'red' : 'black';
return <div style={{ color }} onClick={() => updateActiveDiv(i)}>{div}</div>;
});
return (
<div>
{ divText }
</div>
);
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<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="root"></div>
Pass current text from html
handleClick = (text)=>{
this.setState({activeText:text})
}
Inside create div function add class dynamically
Div className = { stat condition ? Class : no class }

Way to check if first iteration in map

I'm trying to add a class to an element within ReactJS using the map function, but ONLY for the first one in the loop, is this possible / an easy way?
return (
<div key={itemData.itemCode} className="item active">
Want to add 'active' class when the first but for the others dont add it
</div>
)
If you use .map or .forEach then you can do it like this
var List = React.createClass({
render: function() {
var lists = this.props.data.map(function (itemData, index) {
/// if index === 0 ( it is first element in array ) then add class active
var cls = (index === 0) ? 'item active' : 'item';
return <div key={itemData.itemCode} className={ cls }>
{ itemData.itemValue }
</div>;
})
return <div>{ lists }</div>;
}
});
Example
also there is good package called classnames if you need conditionally change classes, like as in your case
var List = React.createClass({
render: function() {
var lists = this.props.data.map(function (itemData, index) {
return <div
key={itemData.itemCode}
className={ classnames('item', { active: index === 0 }) }>
{ itemData.itemValue }
</div>
})
return <div>{ lists }</div>;
}
});
Example

Categories