React Carousel with Multiple Items - reactjs

I'm having problems with my slider with ternary conditions, how may I solve it?
It considers that i'm closing the item before the last clause is done. This is a trying of build a multi-item carousel.
<Carousel className="col-md-7 col-11" indicators="true" controls="false">
{this.props.children.map((rooms,index) =>
(index === 0 || index % 3 === 0) ? <Carousel.Item><h1>First</h1> :
((index+1) % 3 === 0) ? <h1>Last</h1></Carousel.Item> : <h1>Middle</h1>
)
}
</Carousel>

The problem is that this isn't valid JSX.
You can't render an opening <Carousel.Item> tag without a closing tag as part of the same expression. It's clear what you're trying to do here but it can't work because the JSX compiler can't 'know' that the closing tag will ever be rendered, because that's dependant on children. You have to render opening and closing tags as part of the same expression in order for the JSX to compile.
Probably the cleanest thing is to do the grouping of the children in a separate function and then map the result, simply rendering each group inside <Carousel.Item></Carousel.Item>, like so:
function groupIntoThrees (children) {
const output = []
let currentGroup = []
children.forEach((child, index) => {
currentGroup.push(child)
if (index % 3 === 2) {
output.push(currentGroup)
currentGroup = []
}
})
return output
}
... later in render method ...
<Carousel className="col-md-7 col-11" indicators="true" controls="false">
{groupIntoThrees(this.props.children).map((group) => (
<Carousel.Item>
<h1>first: {group[0]}</h1>
<h1>middle: {group[1]}</h1>
<h1>last: {group[2]}</h1>
</Carousel.Item>
)}
</Carousel>

Related

Bootstrap dynamic grid with one element not generated

I am trying to create a dynamic grid for video components, but I can't figure out how to, because I have an element that is not generated dynamically. the first video element is created normally, the rest of the elements are generated using a map function.
I would like to generate rows for every 3 elements including the first.
This is the code I have now:
renderPeers() {
return (
<div className = "participants">
<video id="myVideo" ref={video => this.video = video} controls autoPlay playsInline muted></video>
{
Object.entries(this.state.peers).map(entry => {
const [peerId, peer] = entry
console.log('render peer', peerId, peer, entry)
return (
<video key = {peerId} ref={video => peer.video = video} controls autoPlay playsInline muted></video>
);
})
}
</div>
);
}
The peers state holds objects like this:
{
peer: peerObject, // simple-peer object
peerId: peerID // peerID = socket ID of peer
}
I could generate bootstrap rows and columns using a counter variable, but I have the first element which is not part of the array I use to generate the rest of the elements.
How could I make it so I have rows with 3 columns generated, including the first element?
I'm going to assume, that your state.peers looks like this:
[
{
peer: {...},
peerId: 1
},
{
peer: {...},
peerId: 2
}
]
Because you mention that you have several peers to be rendered.
Nevertheless I'm sure you are using a wrong use of Object.entries, since this function provides an iterator through you object giving you the keys and the values.
For you case this code should fix your problem:
renderPeers() {
return (
<div className = "participants">
<video id="myVideo" ref={video => this.video = video} controls autoPlay playsInline muted></video>
{
this.state.peers.map( item => {
const {peerId, peer}
return (
<video key={peerdId} ref={video => peer.video = video} controls autoPlay playsInline muted></video>
)
})
}
</div>
);
}
Basically your problem was in the Object.entries if you need to find some specific properties in your object is much better for you to use destructuring operator, like this:
const pepe = {
id: 1,
name: 'pepe'
}
const {id,name} = pepe
console.log(id, name), //1 pepe
You should use Object.entries when you want to inspect the structure of the object, usually when is an dynamic object which structure is unknown for you.

program nav menu in react. How do I use logical statements to display html?

I'm using gatsby.js to make a site and using graphql to pull in data for a menu. The menu data is working fine however when I try to add a submemu with logical conditions it outputs the html instead of rendering it. So instead of showing the unordered submenu list, it outputs <ul> and </ul> on my site. (where the list items and main ul are working fine)
Here is my menu component code:
const WPMenu = () => (
<StaticQuery
query ={ graphql`
{
wordpressMenusMenusItems(name: {eq: "Main Menu"}) {
name
items {
slug
title
child_items {
slug
title
}
}
}
}`
}
render = { data => (
<nav role="navigation">
<ul>
{data.wordpressMenusMenusItems.items.map(item => (
<li key={item.title}>
{item.child_items ? <Link aria-haspopup="true" to={`/${item.slug}`}>{item.title}</Link> : <Link to={`/${item.slug}`}>{item.title}</Link>}
{item.child_items ? '<ul>' : "" } //this line is not working
{item.child_items && item.child_items.map ( child =>
<li key={child.title}><Link to={`/${child.slug}`} >{child.title}</Link></li>
)}
{item.child_items ? '</ul>' : "" } //this line is not working
</li>
))}
</ul>
</nav>
)}
/>
);
its the two commented lines {item.child_items ? '<ul>' : "" } //this line is not working
That are giving me problems.
I've tried with this: {item.child_items ? "<ul>" : "" }
and replacing quotes with code ticks as well:
{item.child_items ? `<ul>` : "" }
I'm new to js, so likely there's something I'm missing, but why is it interpreting it as code?
The problem is that is item.child_items is true if is an empty array [] since it is checking it reference exist or not;
you can use item.child_items.length > 0 as the condition or simply item.child_items.lengthsince 0 is equal to false in js 0 == false //true
{item.child_items.length && '<ul>'}
or
{item.child_items.length > 0 ? '<ul>' : ''}
check the snippet for more info:
const obj = { items : [] }
//even though it is empty it will give you true;
console.log( Boolean(obj.items) )
// not not something is boolean equivilant of that thing
console.log( !!obj.items )
//workaround? using length
//since 0 == false
console.log( "how about length?", Boolean(obj.items.length))
Caveat: if item.child_items doesn't have length property, that line would throw an exception (e.g. item.child_items being undefined or null)

Conditional classes on single instances of component within a loop

I have a loop of components that make a grid and I want some instances to have a background color change triggered by adding a class.
This question gave me ideas but I've yet to get them to work.
I have this basic markup in it.
<div className={`box ${(this.state.backgroundColor ? 'backgroundColor' : null)}`} key={i}>
</div>
And I have an array of the indexes representing the component instances that I want the color changed on
let indexes = [101, 178, 232, 545]
Currently I am doing it with plain JS, via document.querySelector('.box:nth-of-type(101'), but as this method bypasses React I want to change it.
Based on the other question I tried making an array of all the components and setting that to state, then looping through and using the indexes. I don't know how to "access" the component like this and get $$typeof: Symbol(react.element) etc
let compArr = []
this.arr.map((i) => {
compArr.push(
<div className={`box ${(this.state.backgroundColor ? 'backgroundColor' : null)}`} key={i}>
</div>
)
})
this.setState({
compArr: compArr
})
Then later loop over indexes:
indexes.map(index => {
this.state.compArr[index] ===> ??stuck here??
})
Expected Output: So on an array of three instances, say I want the second one only to have the class of backgroundColor:
<div className='box' </div>
<div className='box backgroundColor'</div>
<div className='box'</div>
Only in my case, I want the instances to correlate with the indexes array
How can I set the conditional class to true in those instances I have in the array? Say I have like 500+ instances total.
If I understand you correctly this is proably what you're looking for...
let indexes = [101, 178, 232, 545]
let compArr = this.arr.map((obj, i) => {
return (
<div
className={`box${( indexes.includes(i) ? " backgroundColor" : "")}`}
key={i}
>
</div>
);
})
this.setState({
compArr: compArr
})
If your goal is only to return the given div with the className backgroundColor for every element in this.arr with its index included in the indexes array then...
You only need to iterate over the array once and can perform all the necessary logic you're currently doing performing in two.
You don't need to use Array.push() to an outside collector when using any of the pure functions like map() or reduce() because they return a new version of whatever array is iterated over.
If for whatever reason you want a distinct array of <div>s with an index in indexes then you should use reduce...
let compArr = this.arr.reduce((acc, obj, i) => {
if (!indexes.includes(i)) return acc;
return [
...acc,
<div className="box backgroundColor" key={i}></div>
)]
},[])

Render some text if array.filter returns 0 in React?

I have a filter on an array in the render function in a React component:
someArray.filter(item => {
if (item.name.includes(searchText))return true
}).map(item=>{
return <h1>{item.name}</h1>
});
How can I elegantly display some text along the lines of "No search results" when no items are being returned by the map function?
There are a few ways you can do this. You can use a ternary operator (and also shorten your callbacks):
const filtered = someArray.filter(item =>
item.name.includes(searchText)
);
//Then, in your JSX:
{
filtered.length > 0 ?
filtered.map((item, key) =>
<h1 key={key}>{item.name}</h1>
)
:
<h1>No search results</h1>
}
This checks if there are any filtered results. If so, it will map them to h1s that have the name of the item. If not, then it will simply render a single h1 with the text 'No search results'.
One possible way is, instead of putting this code directly inside JSX render method, put it inside a method and call that method from render.
Like this:
_filterItem(){
const arr = someArray.filter(item => item.name.includes(searchText))
if(!arr.length) return <div>No data found</div>;
return arr.map(item => <h1 key={/*some unique value*/}>{item.name}</h1>)
}
render(){
return(
<div>{this._filterItem()}</div>
)
}
Suggestion:
With filter and map you can use concise body of arrow function instead of block body, for more details check MDN Doc.
Short and concise:
someArray.map(({name}) => name.includes(searchText) && <h1>{name}</h1>)

Is there a better way to use conditionals inside jsx?

I've been learning React for the past few weeks and one thing that I don't like is that I have to use ternary operator for if else inside the render function.
Something like:
function render() {
return (
{x==="Let's go" ? <Go /> : <Hold on />}
)
}
Is there a way I can use a traditional if-else or switch for that matter with jsx syntax in React?
I utilize a few approaches to clean up the render method of more complex components.
1) Use a variable. Before you are actually in the JSX portion (the return) you can use the flexibility of raw JS to build variables. To take your example...
function render() {
let body;
if (x === "Let's go") {
body = <Go />
} else {
body = <Hold on />;
}
return (
<div>
{body}
</div>
);
}
Note that the top level needs to be wrapped, just put an extra div in there.
2) Use a function. This example is probably a little too simplistic but you'll get the idea..
renderBody() {
let body;
if (x === "Let's go") {
body = <Go />
} else {
body = <Hold on />;
}
return (
{body}
)
}
render() {
return (
<div>
{renderBody()}
</div>
)
}
3) Use an array (really a subset of #1) but oftentimes I find scenarios where sometimes I need to return 1 element but other times I need to return a variable number of elements. I will create an array at the top of the function and then push/unshift elements onto the array. Note that any time that you are building an array you must provide a key for each element so that React can update it properly.
let els = [
<div key="always">Always here</div>
];
if (foo) {
els.push(<div key="ifFoo">Conditionally Here</div>)
}
And then you just use the {els} variable in your main JSX.
4) Return null when you don't want anything to render
I prefer this syntax
function render() {
if (x==="Let's go") return <Go />;
return <Hold on />;
}

Resources