Skip to content

Instantly share code, notes, and snippets.

@ricealexander
Last active December 2, 2020 00:02
Show Gist options
  • Save ricealexander/4157c29b697ef37a0db1a047946a4b80 to your computer and use it in GitHub Desktop.
Save ricealexander/4157c29b697ef37a0db1a047946a4b80 to your computer and use it in GitHub Desktop.
Gets an item from an array by its index. For indexes outside of the array, it loops the index.
function getItem (array, index) {
if (typeof index !== 'number' || Number.isNaN(index)) {
throw new TypeError(`Expected index to be a Number. Instead got "${index}"`)
}
if (Math.abs(index) === Infinity) {
throw new ReferenceError(`Cannot access item at index "${index}"`)
}
const {length} = array
let wrappedIndex = index
// wrappedIndex is a copy of the provided index
// first handle the lower-bounds and then handle the upper-bounds
// this is because lower bounds formula returns array.length when index is -array.length,
// which causes a ReferenceError
if (wrappedIndex < 0) {
wrappedIndex = length + (wrappedIndex % length)
}
if (wrappedIndex >= length) {
wrappedIndex = wrappedIndex % length
}
return array[wrappedIndex]
}
const fruits = [
'apple', // 0
'banana', // 1
'cherry', // 2
'dragonfruit', // 3
'elderberry', // 4
'fig', // 5
'grape', // 6
'honeydew', // 7
] // length: 8
getItem(fruits, 0) // apple
getItem(fruits, 2) // cherry
getItem(fruits, 8) // apple
getItem(fruits, -2) // grape
getItem(fruits, -8) // apple
getItem(fruits, 15) // apple
getItem(fruits, -17) // apple
// The original use-case I had from this was for an image slider.
// With sliders, a wrap around effect is often used so the slider loops across its slides infinitely.
// To code this logic, there needs to be range handling for the index
// const nextSlide = this.slides[currentSlide + 1] ?? this.slides[0]
// const previousSlide = this.slides[currentSlide - 1] ?? this.slides[this.slides.length - 1]
//
// That's simple enough, but what if there needs to be a wider gap? Then you start writing logic like:
// const laterSlide = this.slides[currentSlide + 3] ?? this.slides[(currentSlide + 3) % this.slides.length]
// const earlierSlide = this.slides[currentSlide - 3] ?? this.slides[this.slides.length + ((currentSlide - 3) % this.slides.length)]
//
// In my case, the client had many slides. Pressing the arrows or clicking the icon would iterate to the
// previous or next slide, but the client wanted a way to navigate faster with a shortcut.
// The proposal was that Shift-clicking or Shift-arrowing would jump 3 slides instead.
//
// I talked the client out of it, but came up with this helper and have used it any other time I've needed to wrap indexes.
// const nextSlide = getItem(this.slides, currentSlide + 1)
// const previousSlide = getItem(this.slides, currentSlide - 1)
// const laterSlide = getItem(this.slides, currentSlide + 3)
// const earlierSlide = getItem(this.slides, currentSlide - 3)
//
// Regardless of the use-case, no worrying is needed regarding how the slides will wrap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment