Conditional classes on single instances of component within a loop - reactjs

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>
)]
},[])

Related

Get access to an array in an array from REST API

Hi I'm trying to display variants in two separate categories Color and Size, to do this I need to get data from the api, I can access "attributes", but I would like to be able to access 0 and 1 and map them, I have no idea how to do this.
{variants.length > 1 ? (
variants.attributes.map(({ name, values }) => (
<ProductOptions
key={`key-${name}`}
name={name}
values={values}
selectedOptions={selectedOptions}
setOptions={setOptions}
/>
))
) : (
<Fragment />
)}
Thank you so much!
As i understand the output of Array with 6 elements where each of that 6 has attributes and attributes is yet another array and you want to loop through those attributes so you need 2 loops. One to loop through parent array and seconds inside the child.
variants.map((variant) => {
variant.attributes.map((attribute) => {
console.log('attribute: ', attribute);
console.log('attribute id: ',attribute.id);
});
});
p.s. you may use forEach https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach but it has little difference in this case.
Also it seems like you working with ReactJS and building some JSX to put into rendered jsx. I would argue to form the array upfront and in final jsx just insert the rows, so your final jsx will be more readable, especially in cases of double loops or any more complex jsx.
const attributes = [];
variants.map((variant) => {
variant.attributes.map((attribute) => {
console.log('attribute: ', attribute);
console.log('attribute id: ',attribute.id);
attributes.push(<div key={attribute.id}>{attribute.id}</div>)
});
});
// later
return (
<div>
{attributes}
</div>
)

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.

React save the value of current state and make it unchangeable

I am new to React and I have a problem considering saving the value of current state. I am not sure if it is possible.
To summarise, I have build up a function which returns a HTML element as below. The idea is to save a div on each click.
createQuestion(){
let CurrentQuestion=this.state.freetextinput;
return this.state.values.map((el, i) =>
<div key={i} className="QuestionBox">
{CurrentQuestion}
</div>
)
}
The value of freetextinput changes on each click. The idea is to save all the value of freetextinput. Say, the values of freetextinput are 1, 2, 3. I would like to have all of the three values:
1
2
3
However, I only get something like this:
3
3
3
I wonder, if there is a way to save/store the value of this.state.freetextinput, such that I can get all the three values.
The new question needs to be pushed into an array before it can be mapped. Might something like this work for you?
var savedQuestions = []
createQuestion(q){
savedQuestions.push(q)
this.setState({savedQuestions: savedQuestions}, () => {
var questionElements = this.state.savedQuestions.map((el, i) =>
<div key={i} className="QuestionBox">
{el}
</div>
)
})
return questionElements
}
Save every input of free text input into an array, that way you would get access to all the values:
You can check this code sandbox: https://codesandbox.io/s/j835121kv
You should map this.state.freetextinput directly :
Since freetextinput is a string, you will need to convert it to an array first using split
createQuestion(){
return this.state.freetextinput.split(', ').map(el =>
<div key={el} className="QuestionBox">
{el}
</div>
)
}
This will work if your string looks like : "1, 2, 3"
If your string is "123" then use split('') instead of split(', ')

Append array of values to the current array in a state in React JS

I have the state values as
this.state = {
emp: [
{id: "1", name: "A"}
{id: "2", name: "B"}
{id: "3", name: "B"}
]
}
How can I add an array like var arr = {id:"4", name:"D"} to the state emp without removing the current values of array. I just want to append the new array of values to the current state array. Can anyone help?
In modern JavaScript you can use the spread operator:
Add a single item
addItem = item => {
this.setState({
emp: [
...this.state.emp,
item
]
})
}
Add multiple items:
addItems = items => {
this.setState({
emp: [
...this.state.emp,
...items
]
})
}
The spread operator places all the elements in this.state.emp in a new array instance and item gets appended as the last element.
You should not mutate a component's state with other means than setState as your rendered data will get out of sync.
just use concat
this.setState({ emp: this.state.emp.concat('new value') })
The reasons why concat is better than push, unshift are
Array.push
Array.prototype.push allows us to push elements to the end of an
array. This method does not return a new copy, rather mutates the
original array by adding a new element and returns the new length
property of the object upon which the method was called.
Array.unshift
To add elements to the very beginning of an array. Just as push, unshift does not return a new copy of the modified array, rather the new length of the array
Both the ways changes the mutation state of an array. A mutation term is meant to be unchanged because it is our original source.
array.concat
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
However, You Object.assign() too, that creates a deep copy of object assigned to it.
let emp = Object.assign([],this.state.emp); //type of an array
result
You need to update if using functional setState(since you are updating state based on prevState) and spread syntax like
this.setState(prevState => ({
emp: [
...prevState.emp,
{id:"4",name:"c"}
]
}))
If using a functional component, voila a simple example:
const [hiddenDivs, setHiddenDivs] = useState([1, 2, 3, 4]);
const handleCatDivTitleClick = (divNum: number) => {
if (hiddenDivs.includes(divNum)) {
setHiddenDivs(hiddenDivs.filter((d) => d !== divNum)); //REMOVE FROM ARRAY
} else {
setHiddenDivs([...hiddenDivs, divNum]); //ADD TO ARRAY
}
};
<div class={`${style.catDiv} ${hiddenDivs.includes(1) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(1)}>
Imagine a list of categories like this. All begin "hidden" (shrunk-up).
</div>
</div>
<div class={`${style.catDiv} ${hiddenDivs.includes(2) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(2)}>
You want to shrink/expand category based on clicking title.
</div>
</div>
<div class={`${style.catDiv} ${hiddenDivs.includes(3) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(3)}>
Basically, a home-rolled accordian-type display.
</div>
</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>)

Resources