TLDR: I'm mapping over an array of arrays and mapping over each of those arrays to render data. React is rendering the each element of each array in order, so that the entire first array is rendered before the second array begins rendering. I would like to render the first element of each array, then the second element of each array, etc.
I'm trying to build a masonry component! From what I can tell, these are traditionally made by calculating the length of each column, and appending new items to the shortest column, then re-calculating and doing it again.
This is an obviously expensive process, and a hacky solution I came up with is:
Sort data (images with known heights) from lowest to highest
Split these evenly into a number of arrays equal to the desired number of columns (end result may have columns of different lengths, but it will approximate close enough for my needs)
Randomize the arrays to give variation in heights
Display data from arrays in flexbox columns
Here's the relevant code for that:
const { columns, height, width } = useStore()
const [ content, setContent ] = useState<any[]>([])
useEffect(() => {
// Sort data by image height
data.sort((a: item, b: item) =>
a.images.downsized.height - b.images.downsized.height
)
// Create array of arrays
let columnsArray: any[] = []
for (let i = 0; i < columns; i++) {
let newArray: item[] = []
columnsArray.push(newArray)
}
// Populate each array with images
let index = 0
data.forEach((item: item) => {
columnsArray[index].push(item)
if (index == columnsArray.length - 1) { index = 0 }
else { index++ }
})
// Randomize each array to give masonry appearance
const shuffle = (array: any[]) => {
// Fisher-Yates algorithm
let m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--)
t = array[m]
array[m] = array[i]
array[i] = t
}
return array
}
columnsArray.forEach(array => shuffle(array))
// Move new data structure to state
setContent(columnsArray)
}, [data])
This works really well as it performs pretty light computations and leverages CSS and flexbox to make up the difference.
The problem is that it renders each column in order. If I'm rendering two columns of 100 items each, it doesn't make sense to load the 100th item of the first column before the 1st item of the second.
Here's the code for rendering:
{content.length > 0 && content.map((contentArray: item[]) =>
<Col>
{contentArray.map((item: item) =>
<MasonryItem
src={item.images.downsized.url}
alt={item.title}
/>
)}
</Col>
)}
Instead, I'd like React to render the first item of the first array, then the first item of the second, etc.
Is there a way to do this? Thanks in advance!
Related
I have a code that converts user input to an array then does a fetch request. The rest of the code only works when the array has a quantity (as a string) and a unit (as a string). So for example, it works when I type in "1 whole apple" or "1 oz chicken thigh" but it breaks when it's just "apple". How can I set up a checkpoint to add "1" and "whole" to the beginning of the array when quantity and unit are missing?
const [input, setInput] = useState("")
const foodName = []
const searchArray = []
// This part below separates user input by space then forms an array
const inputArray = input.split(/(\s+)/).filter(e => String(e).trim())
// This part below forms a new array with only the quantity and the unit, or nested arrays if the user inputs more than one item, which obviously breaks if there is no quantity.
const array = inputArray.reduce(
(arrays, value) => (
isFinite(value)
? arrays.push([value])
: arrays[arrays.length - 1].push(value),
arrays
),
[]
)
// This part below combines food name to a single element if it has more than one word i.e. apple pie.
for (var i = 0; i < array.length; i++) {
const foodName = array[i].splice(2).join(' ')
foodArray.push(foodName)
const wholeArray = array[i].concat(foodArray[i])
searchArray.push(wholeArray)
}
Making the fetch request etc.
Thanks in advance!
So I ended up adding an error message since I could not find a solution to this.
if (!isFinite(inputArray[0])) {
setErrorMessage('Be sure to specify quantity and unit!')
return
}
const array = inputArray.reduce(
(arrays, value) => (
isFinite(value)
? arrays.push([value])
: arrays[arrays.length - 1].push(value),
arrays
),
[]
)
I want to map my nested json from a certain index
For example if my json is the following:
{"data":[{id:1,name:"NAV"},{id:2,name:"Rick"},{id:3,name:"Ceil"}]}
I want to start mapping this json from 1st index i.e. id =1 name :rick
and map it till nav so the mapping is in the following order:
{id:2,name:"Rick"}
{id:3,name:"Ceil"}
{id:1,name:"NAV"}
Something like a circular array or queue
I have no idea how to implement this mapping in react native or js.
How I am currently mapping my json
renderArtciles=()=>{
return this.state.dataSource.map((item,i)=>{
<Animated.View key={item._id} >
<View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style{styles.image}/>
</View>
<View style={styles.inner}>
<ShareItem id={item._id} />
<View style={styles.inner}>
<Text style={styles.titleArrtibute}>Trending</Text>
<Text style={styles.titleText} >{item.title}</Text>
<View>
<Text style={styles.body}>{item.body}</Text>
</View>
</View>
</View >
</Animated.View>
});
}
Here's a quick way to pop off the first element from an array and push it to the back.
Note: These examples modify the original collection.
If you do not want to modify the original you will need to create a clone of the array first. Or you can go with the "walking the array" approach I've added below.
Overall I do not think you are looking to map in this instance, the real question seems to be sorting or iterating your array in a specific way where it loops.
Once you have sorted or iterated your array you can then do your mapping to extract the data you are after.
Example - Rotate a Single Element
const cats = ['Bob', 'Willy', 'Mini'];
cats.push(cats.shift());
console.log(cats);
Example - Rotate Multiple Elements
This is if you want to cycle more than 1 element from the front to the back of the array (an example has been attached at the bottom to demonstrate cloning an array).
This will cut off the given amount (2 in this example) and then uses the spread operator to push the new cut off items from the array back into the array. We use the spread operator because splice returns an array and we don't want a jagged semi-multidimensional array.
const cats = ['Bob', 'Willy', 'Mini', 'Dingus'];
cats.push(...cats.splice(0, 2));
console.log(cats);
Example - Nested Data
Since Javascript stores objects by reference we can store variables and apply the same techniques from above to directly modify the original object.
ie: Notice how we assign a new variable data and modify it but log the original object namesInData which is now changed.
const namesInData = {data:[{name: 'Bob'}, {name: 'Willy'}, {name: 'Mini'}]};
const data = namesInData.data;
data.push(data.shift());
console.log(namesInData);
Example - Walking the Array with a For Loop & Offset
We use the modulus operator % so that when i blows past the end of the array it will wrap back to the front.
const cats = ['Bob', 'Willy', 'Mini', 'Dingus'];
const start = 2;
const length = cats.length;
for(let i = start; i < length + start; i++) {
console.log(cats[i % length]);
}
Cloning an Array
If you do not want to modify the original array you will need to clone it first.
ES6 Way 🎉
Using the spread operator we can assign the spread to a new array.
const data = [...originalData];
Older Non ES6
const data = originalData.slice();
Additional Resources
Pop, Push, Shift, and Unshift Array
Array.prototype.splice not to be confused with slice which does not modify the original array.
Cloning an array
You can do it with just the map function if you find the starting index first.
Note: This solution does not modify the array.
const { dataSource } = this.state
// set startingIndex using whatever method you have
// to find the index of the first item
const startingIndex = (dataSource.findIndex(item => item.name === 'NAV') + 1) % dataSource.length
// underscore to ignore the current item,
// since we'll index to a different item instead
return dataSource.map((_, index) => {
const shiftedIndex = (startingIndex + index) % dataSource.length
const item = dataSource[shiftedIndex]
return {/* render code */}
})
I don't know if there's a way to do it with .map (my instinct is no) but you could do something like the below with a for loop:
let index = 1; //whatever index you want to start from
for (let i = 0; i < data.length; i++) {
//do all of your stuff referencing data[index] not data[i]
//at the end of whatever do:
index === data.length - 1 ? index = 0 : index++;
}
Give it a go, something along these lines should work.
Note: This uses the array index, and not your object id.
Note this is not a duplicate of How to extend an existing JavaScript array with another array, without creating a new array? because I'm looking to have a nested array, not to simply extend an array with another array to result into 1 array. Please read the question properly before you mark this as duplicate.
I'm looping through rows in a (Google) sheet to collect values, and would like to add each row as array to an array, which should result in an output like this (simplified example to illustrate):
array_main = [[row1_cell1,row1_cell2,row1_cell3], [row2_cell1,row2_cell2,row2_cell3], ...]
I first tried this with .push, which adds the values, but not as array:
accounts_last_row = 10
accounts_array = []
for (var i = 0; i < accounts_last_row; ++i) {
if ((accounts_range[i][1] == 'test') {
accounts_array.push([ [accounts_range[i][1]],[accounts_range[i][2]] ])
}
}
I'm aware similar questions have been asked, but most of them simply recommend using .concat to merge 2 arrays. I tried this as well but it doesn't add anything to the array:
...
if ((accounts_range[i][1] == 'test') {
accounts_array.concat( [accounts_range[i][1]],[accounts_range[i][2]] )
}
...
What am I missing? Thanks in advance.
You almost had it, inner arrays are simple ones, you had too many brackets.
Try like this:
accounts_array.push( [accounts_range[i][1],accounts_range[i][2]] );
the code above will work to add rows.
If you want to add data as a single column the you will have to change the brackets like this:
accounts_array.push( [accounts_range[i][1]],[accounts_range[i][2]] );
This type of operation can be done neatly with Array#filter and Array#push and apply:
const results = [];
const colIndexToTest = /** 0, 1, etc. */;
const requiredValue = /** something */;
SpreadsheetApp.getActive().getSheets().forEach(
function (sheet, sheetIndex) {
var matchedRows = sheet.getDataRange().getValues().filter(
function (row, rowIndex) {
// Return true if this is a row we want.
return row[colIndexToTest] === requiredValue;
});
if (matchedRows.length)
Array.prototype.push.apply(results, matchedRows);
});
// Use Stackdriver to view complex objects properly.
console.log({message: "matching rows from all sheets", results: results});
The above searches the given column of all rows on all sheets for the given value, and collects it into a 2d array. If all rows are the same number of columns, this array would be directly serializable with Range#setValues.
This code could have used map instead of forEach and the push.apply, but that would place empty or undefined elements for sheet indexes that had no matches.
I'm assuming if account-range[i][1] is 'test' copy the entire row to accounts_array. Drop the second index.
accounts_last_row = 10
accounts_array = []
for (var i = 0; i < accounts_last_row; ++i) {
if ((accounts_range[i][1] == 'test') {
accounts_array.push(accounts_range[i])
}
}
I have two observable arrays, and I need to remove elements from the first one and push to the second one and vice versa. But when I do so, the alphabetical sorting is messed up.
self.allCourses = ko.observableArray([]);
self.selectedCourses = ko.observableArray([]);
I will interchange courses between the two arrays, and using this :
self.sortArrays = function(){
self.allCourses.sort(function (l, r) {
return l.code() < r.code() ;
});
self.selectedCourses.sort(function (l, r) {
return l.code() < r.code() ;
});
}
not only is it not efficient, but also doesnt work as expected ;I call the function each time I call one of these functions
self.addCourse = function(course){
self.selectedCourses.push(course);
self.allCourses.remove(course);
self.sortArrays();
};
self.removeCourse = function(course){
self.allCourses.push(course);
self.selectedCourses.remove(course);
self.sortArrays();
};
I would consider two approaches.
Keep your data always sorted. Instead of calling .sort(), search for the right location to put the element, and call .splice() to insert it in the right place. This is a O(n) algorithm, but should be fast in practice.
Use something like https://libraries.io/npm/dsjslib to maintain a sorted data structure at all times. This makes insert/delete a O(log(n)) operation. However every operation now has extra complexity.
Which one to use will depend on whether your operations are dominated by the effort of insert/delete, or by running through the list and displaying it. My best guess is that running through the list and displaying it matters more.
Furthermore the next question is whether it is better to do the search by scanning through the array, or by binary search. Scanning is O(n) but branch prediction mistakes cost so much that I've seen it be faster than binary search for inserting into lists of hundreds of elements.
Using knockout, u can also create computed based on your observable array, so you always will have sorted array
self.allCoursesSorted = ko.computed(function(){
return this.allCourses.sort(function (l, r) {
return l.code() < r.code() ;
});
}, this);
for selected courses you can use same approach but with filter
self.allCoursesSelected = ko.computed(function(){
return ko.utils.arrayFilter(this.allCoursesSorted(),
function (item) {
return item.selected === true;
});
}, this);
When removing an item from an array, you will never have to do a re-sort.
Instead of pushing and re-sorting, you could insert an item using your sort definition.
You'll only need to define the sorted inject function, since knockout observable arrays already have a remove method:
const sorter = (a, b) => a > b ? 1 : a < b ? -1 : 0;
const leftNumbers = ko.observableArray(
[3,5,1,2].sort(sorter)
);
const rightNumbers = ko.observableArray(
[4,1,3,5].sort(sorter)
);
// There are many ways to write this function, which you can probable
// find on stack overflow. The destructuring probably makes this slower
// than just re-sorting. I'll leave it up to you to optimize for performance.
const injectSorted = (sorter, arr, nr) => {
const pos = arr.findIndex(x => sorter(x, nr) > -1);
if (pos === -1) return arr.concat(nr);
return [
...arr.slice(0, pos),
nr,
...arr.slice(pos)
];
};
// Notice how we don't need to re-sort
const moveFromTo = (arr1, arr2) => x => {
arr2(injectSorted(sorter, arr2(), arr1.remove(x)));
};
ko.applyBindings({ leftNumbers, rightNumbers, moveFromTo });
div { display: flex; justify-content: space-around; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p>Click numbers to move between lists</p>
<div>
<ul data-bind="foreach: leftNumbers">
<li data-bind="click: moveFromTo(leftNumbers, rightNumbers), text: $data"></li>
</ul>
<ul data-bind="foreach: rightNumbers">
<li data-bind="click: moveFromTo(rightNumbers, leftNumbers), text: $data"></li>
</ul>
</div>
I am building a project using React.js as a front-end framework. On one particular page I am displaying a full data set to the user. I have an Array which contains this full data set. It is an array of JSON objects. In terms of presenting this data to the user, I currently have it displaying the whole data set by returning each item of data using Array.map().
This is a step in the right direction, but now I need to display only a portion of the data-set, not the whole thing, I also want some control in terms of knowing how much of the total data set has been displayed, and how much of the data set is yet to be displayed. Basically I am building something like a "view more" button that loads more items of data to the user.
Here is what I am using now where 'feed' represents my Array of JSON objects. (this displays the whole data set.)
return (
<div className={feedClass}>
{
feed.map((item, index) => {
return <FeedItem key={index} data={item}/>
})
}
</div>
);
I am wondering if it is possible to use .map() on only a portion of the array without having to break up the array before hand? I know that a possible solution would be to hold the full data set, and break it off into portions, and then .map() those portions, but is there a way to .map() a portion of the array without having to break it up?
Any and all feedback is appreciated. Thanks!
Do not try to solve this problem with a hack in your mapping step.
Instead, slice() the list to the right length first before the mapping:
class Feed extends React.Component {
constructor(props) {
super(props)
this.handleShowMore = this.handleShowMore.bind(this)
this.state = {
items: ['Item A', 'Item B', 'Item C', 'Item D'],
showItems: 2
}
}
handleShowMore() {
this.setState({
showItems:
this.state.showItems >= this.state.items.length ?
this.state.showItems : this.state.showItems + 1
})
}
render() {
const items = this.state.items.slice(0, this.state.showItems).map(
(item) => <div>{item}</div>
)
return (
<div>
{items}
<button onClick={this.handleShowMore}>
Show more!
</button>
</div>
)
}
}
ReactDOM.render(
<Feed />,
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>
The easiest way in my head is just to use a filter and map
const feed = [1,2,3,4,5,6,7,8,9,10,11]
feed.filter((item, index) => index < 5).map((filteredItem) => //do somthing with filtred item here//)
where 5 is just a number of items you want to get
you could use the slice function before to map the array, it looks like you want to do some pagination there.
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3);
// fruits contains ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
// citrus contains ['Orange','Lemon']
Array.reduce should do what you're asking for. Just change the if statement depending on which range you want.
var excludeAfterIndex = 5;
feed.reduce((mappedArray, item, index) => {
if (index > excludeAfterIndex) { // Whatever range condition you want
mappedArray.push(<FeedItem key={index} data={item}/>);
}
return mappedArray;
}, []);
If you just want to map a portion of an array, you should first filter() your array to obtain the expected portion according to conditions :
array.filter(item => <condition>).map();
Yes, you can map portion of array, based on index. For example:
yourArray = yourArray.map(function (element, index, array) {
if (array.indexOf(element) < yourIndex) {
return {
//logic here
};
} else {
return {
//logic here
};
}
});
You can use slice to get portion of an array:
const data = [1,2,3,4,5,6,7,8,9,10,11]
var updatedData = data.slice(0, 3);
Array#map iterates over all items.
The map() method creates a new array with the results of calling a provided function on every element in this array.
You could use Array#filter
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
for the wanted items and then apply map for the wanted format.
There is no version of the map() function that only maps a partial of the array.
You could use .map() in conjunction with .filter().
You get the index of the current element as the second arg of map and if you have a variable for current page and page size you can quite easily filter the right page from your array without having to really slice it up.
var currentPage = 1;
var pageSize = 25;
dataArray.filter(function(elt, index) {
var upperThreshold = currentPage * pageSize;
var lowerThreshold = currentPage * pageSize - pageSize;
return index < upperThreshold && index > lowerThreshold;
});
Using slice() is better than adding a condition to your map or reduce function, but it still creates an additional, unused copy of that segment of the array. Depending on what you're doing, that might not be desired. Instead, just use a custom map function:
function sliceMap(fn, from, toExclusive, array) {
const len = toExclusive - from;
const mapped = Array(len);
for (let i = 0; i < len; i++) {
mapped[i] = fn(array[i + from], i);
}
return mapped;
};
Note that fn receives the array value and the (now) zero-based index. You might want to pass the original index (i + from). You might also want to pass the full array as a third parameter, which is what Array.map does.
Use this, easy approach
const [limit, setLimit] = useState(false);
const data = [{name: "john}, {name: 'Anna'}]
Here we will have 2 cases:
Display only first data which is John
Display all
data.slice(0, extended ? data.length : 1).map((item, index) => <Text>{item.name}</Text>)
....