How to push specific items in a new array during a map? - javascript

I'm mapping over an array of objects. I only want to display the items that can fit in a single line. The other ones have to be pushed in a new array, that will be displayed elsewhere (a "see more button" + a popover).
In order to do it, I have a ref on the div that encompasses the mapped items. Once its height changes, I know I'm on another line, and that it's time to stop displaying items.
I wrote:
const Component = ({myArray}) => {
let ref = React.useRef();
const { height, width } = useSize(ref);
const availableSpace = 24;
const otherArray = [];
return (
<div ref={ref} style={{display: "flex" flexWrap:"flew-wrap"}}>
{myArray.map({label} => {
height > 24 && otherArray.push({label});
return (
//stuff
)
})}
{otherArray && otherArray.length > 0 && (
<button onClick={()=> showRemainingItemsInAPopover}>
+{otherArray.length}
</button>
)}
)
}
Sadly, React renders all the items, and then pushes all of them again in otherArray.
How to properly separate my array in two in real time?
Thanks!

Maybe you can use filter function :
<div ref={ref}>
{myArray.map({label} => {
height > 24 && otherArray.push({label});
return (
//stuff
)
}).filter(() => height <= 24)}
but i'd like to see more code to you help more.

Not sure how many items are in your array and if you are trying to do this for performance reasons...
Could you not use css to set a max-height: 24px; overflow: hidden; and then use React to toggle the max-height property?
Ie. https://codesandbox.io/s/red-wind-9c94z?fontsize=14&hidenavigation=1&theme=dark

Related

How to get itemSize for VariableSizeList in react-window?

The reason why I want to use react-window is to avoid excessive DOM problem. Also, because I'm using nextjs and React.lazy() doesn't work there. I have a Layout component in which the children are being dynamically generated, you can see the problem being solved here. However, it seems to me that it's not possible to get the height of the children beforehand.
In addition to the LayoutWrapper as shown in the previous question, the VariableSizeList is being setup for me as follow:
const [heights, setHeights] = React.useState<number[]>([])
React.useEffect(() => {
// This will actually returns [] which I don't want to happen.
// I want it to return an array of heights
const data = onMount()
setHeights(data)
}, [onMount])
const Row = ({index, style}: ListChildComponentProps) => (
<div style={style}>{childrenArr[index]}</div>
)
const getItemSize = (index: number) => heights[index]
return (
<AutoSizer disableWidth>
{({height, width}) => (
<VariableSizeList
height={height}
itemCount={childrenArr.length}
itemSize={getItemSize}
width={width}
>
{Row}
</VariableSizeList>
)}
</AutoSizer>
)
Is there any work around solution to this?

Cannot map value of object seen in console

My idea is to click on any of the buttons on the left navbar and whenever the name of the clicked button in the logos object matches any of the names in the items object within projects, then display those objects.
When I click on any of the buttons on the left, I transform that object's active property to true within the logos object. After I've filtered the values, I can see all of the correct values in the console, but I can't loop through them - with a for loop or a map. Oddly enough, when I write filteredValues[0] I am able to output that data to the screen but since I want multiple outputs for some of these clicked values, this is not an option. Any help is appreciated. Thank you!
These are the items that I can't loop through but am getting back when I console log them
These are my projects
These are my logos
const Homepage = () => {
const {state} = useContext(Context)
const {projects,logos} = state;
return (
<>
<div className="container">
<Language />
<div className="homepage">
{logos.map(logo => {
let filteredValues;
if(logo.active == true){
filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name))
filteredValues.map((val) =>{
console.log(val)
return(
<div>{val.title}</div>
)
}) //end of filteredValues.map
} //end of if
}) // end of logos.map
}
</div>
</div>
</>
)}
An Array.map() would always return an Array of same length, irrespective of you return anything or not. If you do not return anything then
it would be a sparse Array with empty values.
Try returning only the array with the required data. Here I have just separated out the Logos logic in a separate variable.
render() {
const Logos = (
<div className="homepage">
logos.reduce((acc, logo) => {
if (logo.active) {
const filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name));
Object.values(projects).forEach(({items}) => {
if (Object.values(items).includes(logo.name)) {
acc.push((<div>{val.title}</div>));
}
});
}
return acc
}, [])
</div>
)
return (
<div className="container">
<Language />
{Logos}
// rest of the code
)
}

JS open tag with if and close it in another one

I have react component that gets index from map function, I'm trying to open div of row tag when my index is even and close it when my index is odd
render() {
return (
{this.props.index%2===0 && (<div className="row mt-1">)} //new row
<div className="col-1">{this.props.title}</div>
<div className="col-5">
<ProgressBar
variant={this.props.color}
now={this.props.now}
max={this.props.max}
label={this.props.label}
/>
</div>
{this.props.index%2===1 && (</div>)} //end of the row
);
}
this code in not compile:
enter image description here
the point is that every row contain two ProgressBar. What is the right way to do it?
You need to deal with whole elements are once, not tags.
This is easier if you break it up into functions.
You can use splice on an array to grab two items at a time.
e.g.
function createRow(elements) {
return (
<div>
{elements.map(createProgressBar)}
</div>
);
}
function createProgressBar(element) {
return (
<div>{element.index}</div>
);
}
function render() {
// ...
const rows = [];
while (myArray.length) {
const thisRow = myArray.splice(0,2);
rows.push(createRow(thisRow));
}
return rows;
}
You should modify the shape of your array into something like this before trying to render it.
[1,2,3,4] => [[1,2],[3,4]]
that way it would be easier for you to wrap it inside div.
see the live demo
Code to transform your flat array into nested array:
const list = [1, 2, 3, 4];
const [state, setState] = React.useState([]);
React.useEffect(() => {
let res = [];
for (let i = 0; i < list.length; i += 2) {
res.push([list[i], list[i + 1]]);
}
setState(res);
}, []);
Render logic:
{state.map(item => {
return (
<div style={{ border: "1px solid black", padding: "1em" }}>
{item.map(i => i)}
</div>
);
})}

React ternary operator used to iterate through a results array (otherwise undefined), is there a different, preferred approach?

I'm new to React and my current code works, but is there a better, "React way" (w/o ternary) of mapping through an empty array, before it has the results (without returning undefined)?
I'm rendering a materialize card for every result returned from an API search. What the code does isn't the issue because it works w/ my current solution.
Thanks in advance.
class NutritonixResultsDisplay extends Component {
render() {
const hits = this.props.nutritionixResults.hits;
return (
<div className='nutritionixResultsDiv'>
{hits
? hits.map((result, i) => (
<div key={i} className='nutritionixResultDiv'>
<Card className='cardDisplay' header={<CardTitle reveal image='' waves='light'/>}
title={result.fields.item_name}reveal={<div><p>{`Calories: ${result.fields.nf_calories}`}</p><p>{`Protein: ${result.fields.nf_protein}`}</p></div>}> <p>This is a link</p>
<p>{`Brand Name: ${result.fields.brand_name}`}</p>
</Card>
</div>
))
: ''}
</div>
);
}
}
export default NutritonixResultsDisplay;
As an alternative you can use || []:
(hits || []).map( // ... etc. )
Just simply make hits an empty array.
const hits = this.props.nutritionixResults.hits || [];

Rendering React Components from Array of Objects

I have some data called stations which is an array containing objects.
stations : [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
]
I'd like to render a ui component for each array position. So far I can write
var stationsArr = []
for (var i = 0; i < this.data.stations.length; i++) {
stationsArr.push(
<div className="station">
{this.data}
</div>
)
}
And then render
render(){
return (
{stationsArr}
)
}
The problem is I'm getting all of the data printing out. I instead want to just show a key like {this.data.call} but that prints nothing.
How can I loop through this data and return a new UI element for each position of the array?
You can map the list of stations to ReactElements.
With React >= 16, it is possible to return multiple elements from the same component without needing an extra html element wrapper. Since 16.2, there is a new syntax <> to create fragments. If this does not work or is not supported by your IDE, you can use <React.Fragment> instead. Between 16.0 and 16.2, you can use a very simple polyfill for fragments.
Try the following
// Modern syntax >= React 16.2.0
const Test = ({stations}) => (
<>
{stations.map(station => (
<div key={station.call} className='station'>{station.call}</div>
))}
</>
);
// Modern syntax < React 16.2.0
// You need to wrap in an extra element like div here
const Test = ({stations}) => (
<div>
{stations.map(station => (
<div className="station" key={station.call}>{station.call}</div>
))}
</div>
);
// old syntax
var Test = React.createClass({
render: function() {
var stationComponents = this.props.stations.map(function(station) {
return <div className="station" key={station.call}>{station.call}</div>;
});
return <div>{stationComponents}</div>;
}
});
var stations = [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
];
ReactDOM.render(
<div>
<Test stations={stations} />
</div>,
document.getElementById('container')
);
Don't forget the key attribute!
https://jsfiddle.net/69z2wepo/14377/
I have an answer that might be a bit less confusing for newbies like myself. You can just use map within the components render method.
render () {
return (
<div>
{stations.map(station => <div key={station}> {station} </div>)}
</div>
);
}
this.data presumably contains all the data, so you would need to do something like this:
var stations = [];
var stationData = this.data.stations;
for (var i = 0; i < stationData.length; i++) {
stations.push(
<div key={stationData[i].call} className="station">
Call: {stationData[i].call}, Freq: {stationData[i].frequency}
</div>
)
}
render() {
return (
<div className="stations">{stations}</div>
)
}
Or you can use map and arrow functions if you're using ES6:
const stations = this.data.stations.map(station =>
<div key={station.call} className="station">
Call: {station.call}, Freq: {station.frequency}
</div>
);
This is quite likely the simplest way to achieve what you are looking for.
In order to use this map function in this instance, we will have to pass a currentValue (always-required) parameter, as well an index (optional) parameter.
In the below example, station is our currentValue, and x is our index.
station represents the current value of the object within the array as it is iterated over.
x automatically increments; increasing by one each time a new object is mapped.
render () {
return (
<div>
{stations.map((station, x) => (
<div key={x}> {station} </div>
))}
</div>
);
}
What Thomas Valadez had answered, while it had provided the best/simplest method to render a component from an array of objects, it had failed to properly address the way in which you would assign a key during this process.
There are couple of way which can be used.
const stations = [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
];
const callList = stations.map(({call}) => call)
Solution 1
<p>{callList.join(', ')}</p>
Solution 2
<ol>
{ callList && callList.map(item => <li>{item}</li>) }
</ol>
Of course there are other ways also available.

Categories

Resources