Lots of UI is a ring: a carousel that loops, a theme toggle that rotates light → dark → system → light, a "next tip" button that never runs out. useCycleList captures that pattern — hold an index into an array and move it forward or backward with wraparound, so stepping off the end lands back at the start and stepping before the start lands on the last item.
Implement useCycleList(items, startIndex = 0). It returns [activeItem, controls], where controls exposes the current index plus next(), prev(), and setIndex(i) — all of which keep the index inside the array's bounds by wrapping.
function useCycleList(items, startIndex = 0) {
// returns [activeItem, { index, next, prev, setIndex }]
}
const [color, { next }] = useCycleList(['red', 'green', 'blue']);
// color === 'red'
next(); // 'green'
next(); // 'blue'
next(); // wraps -> 'red'
const [tab, { prev, setIndex }] = useCycleList(['a', 'b', 'c'], 0);
prev(); // wraps back -> 'c'
setIndex(4); // 4 % 3 === 1 -> 'b'
setIndex(-1); // -> last item 'c'
next past the end returns to index 0; prev before 0 returns to the last index. Modular arithmetic, not clamping.setIndex normalizes — any integer maps into [0, length) via wraparound, so negatives and overshoots are valid.next/prev/setIndex should keep the same identity across renders (wrap them in useCallback) so consumers can pass them to memoized children.items has at least one element.